1
0
mirror of https://github.com/google/nomulus synced 2025-12-23 06:15:42 +00:00

Add screenshot tests for the new registrar console (#2577)

This required updating to a newer version of Selenium, building the
console dist/ folder, and serving that folder.
This commit is contained in:
gbrodman
2024-10-09 12:44:34 -04:00
committed by GitHub
parent 6e77c89cd6
commit c32fb2fc71
84 changed files with 232 additions and 40 deletions

View File

@@ -910,6 +910,9 @@ task fragileTest(type: FilteringTest) {
// Common exclude pattern. See README in parent directory for explanation.
tests = fragileTestPatterns
// Screenshot tests depend on the console being built
dependsOn(rootProject.project('console-webapp').tasks.named('buildConsoleWebapp'))
if (rootProject.findProperty("skipDockerIncompatibleTests") == "true") {
exclude dockerIncompatibleTestPatterns
}

View File

@@ -256,6 +256,7 @@ commons-collections:commons-collections:3.2.2=checkstyle
commons-dbutils:commons-dbutils:1.8.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.17.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-logging:commons-logging:1.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
dev.failsafe:failsafe:3.3.2=testCompileClasspath,testRuntimeClasspath
dnsjava:dnsjava:3.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_linux_x86_64:4.6.0=testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_macosx_x86_64:4.6.0=testRuntimeClasspath
@@ -326,9 +327,11 @@ io.opentelemetry:opentelemetry-api:1.42.1=testCompileClasspath,testRuntimeClassp
io.opentelemetry:opentelemetry-bom:1.33.0=testRuntimeClasspath
io.opentelemetry:opentelemetry-context:1.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-context:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-logging:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.42.1=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
@@ -400,7 +403,7 @@ org.apache.beam:beam-vendor-grpc-1_60_1:0.2=compileClasspath,deploy_jar,nonprodC
org.apache.beam:beam-vendor-guava-32_1_2-jre:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-compress:1.26.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-csv:1.12.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-exec:1.3=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-exec:1.4.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-lang3:3.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.12.0=testCompileClasspath,testRuntimeClasspath
org.apache.ftpserver:ftplet-api:1.2.0=testCompileClasspath,testRuntimeClasspath
@@ -493,7 +496,8 @@ org.joda:joda-money:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,no
org.json:json:20160212=soy
org.json:json:20240303=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.18.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:0.3.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:0.3.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
org.jspecify:jspecify:1.0.0=testCompileClasspath,testRuntimeClasspath
org.junit-pioneer:junit-pioneer:2.3.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.11.2=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.11.2=testCompileClasspath,testRuntimeClasspath
@@ -530,16 +534,24 @@ org.pcollections:pcollections:3.1.4=annotationProcessor,errorprone,nonprodAnnota
org.postgresql:postgresql:42.7.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-api:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chrome-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-edge-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-firefox-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-ie-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-java:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-opera-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-remote-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-safari-driver:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-support:3.141.59=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-api:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chrome-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-chromium-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v127:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v128:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v129:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-devtools-v85:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-edge-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-firefox-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-http:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-ie-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-java:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-json:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-manager:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-os:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-remote-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-safari-driver:4.25.0=testCompileClasspath,testRuntimeClasspath
org.seleniumhq.selenium:selenium-support:4.25.0=testCompileClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:1.7.36=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testRuntimeClasspath
org.slf4j:slf4j-api:2.0.16=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath

View File

@@ -34,17 +34,20 @@ public final class RegistryTestServer {
public static final ImmutableMap<String, Path> RUNFILES =
new ImmutableMap.Builder<String, Path>()
.put("/index.html",
.put(
"/index.html",
PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/index.html"))
.put("/error.html",
.put(
"/error.html",
PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/html/error.html"))
.put("/assets/js/*", RESOURCES_DIR.resolve("google/registry/ui"))
.put("/assets/css/*", RESOURCES_DIR.resolve("google/registry/ui/css"))
.put("/assets/sources/*", PROJECT_ROOT)
.put("/assets/*", PROJECT_ROOT.resolve("core/src/main/java/google/registry/ui/assets"))
.put("/console/*", PROJECT_ROOT.resolve("console-webapp/staged/dist"))
.build();
private static final ImmutableList<Route> ROUTES =
public static final ImmutableList<Route> ROUTES =
ImmutableList.of(
// Frontend Services
route("/whois/*", FrontendServlet.class),

View File

@@ -0,0 +1,162 @@
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.webdriver;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.server.Fixture.BASIC;
import com.google.common.collect.ImmutableMap;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.server.RegistryTestServer;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
/**
* Tests for the 2024 registrar console, served from the Angular staged distribution.
*
* <p>When running the tests locally, be sure to build the console webapp first (the
* "buildConsoleWebapp" Gradle command, or the "npm run build" command) otherwise the tests will
* fail.
*
* <p>In general, for tests, we load the home page then click on elements to transfer pages. This is
* because we aren't really serving the webapp the same way that an actual webserver (e.g. Express)
* would, we're just statically serving the distribution. As a result, we cannot load URLs, e.g.
* "/#/settings", directly. Put another way, because we don't have a service actually serving the
* console, we have to access /console/index.html directly to load the raw file itself, rather than
* accessing /console.
*
* <p>NB: the calls to Thread.sleep are unfortunate but seem to be necessary. Often when we click on
* something, that element is highlighted for a very small amount of time after we click it, with
* the color fading over a short period of time. We must wait for the highlighting to fade to get
* cnosistent images to avoid spurious failures.
*/
public class ConsoleScreenshotTest extends WebDriverTestCase {
@RegisterExtension
final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(RegistryTestServer.ROUTES)
.setFixtures(BASIC)
.setEmail("Marla.Singer@crr.com") // from makeRegistrarContact3
.setRegistryLockEmail("Marla.Singer.RegistryLock@crr.com")
.build();
@BeforeEach
void beforeEach() throws Exception {
server.setRegistrarRoles(ImmutableMap.of("TheRegistrar", RegistrarRole.ACCOUNT_MANAGER));
loadHomePage();
}
@RetryingTest(3)
void index() throws Exception {
driver.diffPage("page");
}
@RetryingTest(3)
void index_registrarSelectDropdown() throws Exception {
selectRegistrar();
driver.diffPage("selectedRegistrar");
assertThat(driver.getCurrentUrl()).endsWith("?registrarId=TheRegistrar");
}
@RetryingTest(3)
void dums_mainPage() throws Exception {
clickSidebarElementByName("Domains");
driver.diffPage("noRegistrarSelected");
selectRegistrar();
driver.waitForElementToNotExist(By.tagName("mat-spinner"));
Thread.sleep(50);
driver.diffPage("registrarSelected");
driver.findElement(By.cssSelector("mat-table button")).click();
Thread.sleep(100);
driver.diffPage("actionsButtonClicked");
}
@RetryingTest(3)
void settingsPage() throws Exception {
clickSidebarElementByName("Settings");
driver.diffPage("noRegistrarSelected");
selectRegistrar();
driver.diffPage("registrarSelected_contacts");
driver.findElement(By.cssSelector("a[routerLink=\"whois\"]")).click();
Thread.sleep(500);
driver.diffPage("registrarSelected_whois");
driver.findElement(By.cssSelector("a[routerLink=\"security\"]")).click();
Thread.sleep(500);
driver.diffPage("registrarSelected_security");
}
@RetryingTest(3)
void billingInfo() throws Exception {
clickSidebarElementByName("Billing Info");
driver.diffPage("noRegistrarSelected");
selectRegistrar();
driver.diffPage("registrarSelected");
}
@RetryingTest(3)
void resources() throws Exception {
clickSidebarElementByName("Resources");
driver.diffPage("page");
}
@RetryingTest(3)
void support() throws Exception {
clickSidebarElementByName("Support");
driver.diffPage("page");
}
@RetryingTest(3)
void globalRole_registrars() throws Exception {
server.setGlobalRole(GlobalRole.SUPPORT_LEAD);
loadHomePage();
driver.diffPage("homePage");
clickSidebarElementByName("Registrars");
driver.diffPage("registrarsPage");
}
private void clickSidebarElementByName(String name) throws Exception {
WebElement appContainer =
driver.findElement(By.cssSelector("mat-sidenav-container.console-app__container"));
List<WebElement> sidebarElems =
appContainer.findElements(By.cssSelector("mat-tree-node,mat-nested-tree-node"));
for (WebElement elem : sidebarElems) {
if (elem.getText().contains(name)) {
elem.click();
break;
}
}
Thread.sleep(100);
}
private void loadHomePage() throws InterruptedException {
driver.get(server.getUrl("/console/index.html"));
driver.waitForElementToNotExist(By.tagName("mat-progress-bar"));
}
private void selectRegistrar() throws InterruptedException {
driver.findElement(By.cssSelector("div.console-app__registrar input")).click();
Thread.sleep(200);
driver.diffPage("selectorOpen");
driver.findElement(By.cssSelector("div.mat-mdc-autocomplete-panel mat-option")).click();
Thread.sleep(200);
}
}

View File

@@ -43,7 +43,7 @@ class DockerWebDriverExtension implements BeforeAllCallback, AfterAllCallback {
private static URL getWebDriverUrl() {
// TODO(#209): Find a way to automatically detect the version of docker image
GenericContainer container =
new GenericContainer("selenium/standalone-chrome:3.141.59-mercury")
new GenericContainer("selenium/standalone-chrome:4.25")
.withFileSystemBind("/dev/shm", "/dev/shm", BindMode.READ_WRITE)
.withExposedPorts(CHROME_DRIVER_SERVICE_PORT)
.waitingFor(Wait.forHttp("/").withStartupTimeout(Duration.of(20, ChronoUnit.SECONDS)));
@@ -53,7 +53,7 @@ class DockerWebDriverExtension implements BeforeAllCallback, AfterAllCallback {
url =
new URL(
String.format(
"http://%s:%d/wd/hub",
"http://%s:%d",
container.getContainerIpAddress(),
container.getMappedPort(CHROME_DRIVER_SERVICE_PORT)));
} catch (MalformedURLException e) {
@@ -65,7 +65,7 @@ class DockerWebDriverExtension implements BeforeAllCallback, AfterAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
ChromeOptions chromeOptions = new ChromeOptions().setHeadless(true);
ChromeOptions chromeOptions = new ChromeOptions().addArguments("--headless=new");
webDriver = new RemoteWebDriver(WEB_DRIVER_URL, chromeOptions);
}

View File

@@ -22,11 +22,13 @@ import com.google.common.collect.ImmutableMap;
import google.registry.module.frontend.FrontendServlet;
import google.registry.server.RegistryTestServer;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junitpioneer.jupiter.RetryingTest;
import org.openqa.selenium.By;
/** Registrar Console Screenshot Differ tests. */
@Disabled
public class OteSetupConsoleScreenshotTest extends WebDriverTestCase {
@RegisterExtension

View File

@@ -22,6 +22,7 @@ import static google.registry.util.NetworkUtils.pickUnusedPort;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HostAndPort;
import google.registry.model.console.GlobalRole;
import google.registry.model.console.RegistrarRole;
import google.registry.model.console.User;
import google.registry.model.console.UserRoles;
@@ -139,6 +140,15 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
OidcTokenAuthenticationMechanism.setAuthResultForTesting(AuthResult.createUser(user));
}
/** Sets the current user's global role. */
public void setGlobalRole(GlobalRole globalRole) {
user =
user.asBuilder()
.setUserRoles(new UserRoles.Builder().setGlobalRole(globalRole).build())
.build();
OidcTokenAuthenticationMechanism.setAuthResultForTesting(AuthResult.createUser(user));
}
/**
* @see TestServer#getUrl(String)
*/
@@ -231,11 +241,15 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
return this;
}
public Builder setRoutes(ImmutableList<Route> routes) {
checkArgument(!routes.isEmpty(), "Must include at least one route");
this.routes = routes;
return this;
}
/** Sets the list of servlet {@link Route} objects for {@link TestServer}. */
public Builder setRoutes(Route... routes) {
checkArgument(routes.length > 0);
this.routes = ImmutableList.copyOf(routes);
return this;
return setRoutes(ImmutableList.copyOf(routes));
}
/** Sets an ordered list of fixtures that should be loaded on startup. */

View File

@@ -52,7 +52,6 @@ public final class WebDriverPlusScreenDifferExtension
implements BeforeEachCallback,
AfterEachCallback,
WebDriver,
org.openqa.selenium.interactions.HasInputDevices,
TakesScreenshot,
JavascriptExecutor,
HasCapabilities {
@@ -72,7 +71,7 @@ public final class WebDriverPlusScreenDifferExtension
// Default size of the browser window when taking screenshot. Having a fixed size of window can
// help make visual regression test deterministic.
private static final Dimension DEFAULT_WINDOW_SIZE = new Dimension(1200, 2000);
private static final Dimension DEFAULT_WINDOW_SIZE = new Dimension(1920, 1200);
private static final String GOLDENS_PATH =
getResource(WebDriverPlusScreenDifferExtension.class, "goldens/chrome-linux").getFile();
@@ -133,6 +132,16 @@ public final class WebDriverPlusScreenDifferExtension
return waitForElementWithCondition(by, Predicates.alwaysTrue());
}
/** Waits for the removal of an element (e.g. a loading bar). */
void waitForElementToNotExist(By by) throws InterruptedException {
while (true) {
if (findElements(by).isEmpty()) {
return;
}
Thread.sleep(WAIT_FOR_ELEMENTS_POLLING_INTERVAL_MS);
}
}
/**
* Executes an action and waits indefinitely for the replacement of an <em>existing</em> element.
*/
@@ -283,16 +292,6 @@ public final class WebDriverPlusScreenDifferExtension
return driver.manage();
}
@Override
public org.openqa.selenium.interactions.Keyboard getKeyboard() {
return ((org.openqa.selenium.interactions.HasInputDevices) driver).getKeyboard();
}
@Override
public org.openqa.selenium.interactions.Mouse getMouse() {
return ((org.openqa.selenium.interactions.HasInputDevices) driver).getMouse();
}
@Override
public <X> X getScreenshotAs(OutputType<X> target) throws WebDriverException {
return ((TakesScreenshot) driver).getScreenshotAs(target);

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 24 KiB