Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add screenshot tests for the new registrar console #2577

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
179 changes: 96 additions & 83 deletions core/gradle.lockfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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.
*/
public class ConsoleScreenshotTest extends WebDriverTestCase {

@RegisterExtension
final TestServerExtension server =
new TestServerExtension.Builder()
.setRunfiles(RegistryTestServer.RUNFILES)
.setRoutes(RegistryTestServer.ROUTES)
.setFixtures(BASIC)
.setEmail("[email protected]") // from makeRegistrarContact3
.setRegistryLockEmail("[email protected]")
.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"));
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand All @@ -53,7 +53,7 @@ private static URL getWebDriverUrl() {
url =
new URL(
String.format(
"http://%s:%d/wd/hub",
"http://%s:%d",
container.getContainerIpAddress(),
container.getMappedPort(CHROME_DRIVER_SERVICE_PORT)));
} catch (MalformedURLException e) {
Expand All @@ -65,7 +65,7 @@ private static URL getWebDriverUrl() {

@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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
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;
Expand Down Expand Up @@ -139,6 +140,15 @@ public void setRegistrarRoles(Map<String, RegistrarRole> registrarRoles) {
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)
*/
Expand Down Expand Up @@ -231,11 +241,15 @@ Builder setRunfiles(ImmutableMap<String, Path> runfiles) {
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. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ public final class WebDriverPlusScreenDifferExtension
implements BeforeEachCallback,
AfterEachCallback,
WebDriver,
org.openqa.selenium.interactions.HasInputDevices,
TakesScreenshot,
JavascriptExecutor,
HasCapabilities {
Expand All @@ -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();
Expand Down Expand Up @@ -133,6 +132,16 @@ WebElement waitForElement(By by) throws InterruptedException {
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.
*/
Expand Down Expand Up @@ -283,16 +292,6 @@ public Options manage() {
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);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions db/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:1.7.36=testCompileClasspath
org.slf4j:slf4j-api:2.0.16=deploy_jar,runtimeClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.20.1=testCompileClasspath,testRuntimeClasspath
org.testcontainers:jdbc:1.20.1=testCompileClasspath,testRuntimeClasspath
org.testcontainers:junit-jupiter:1.20.1=testCompileClasspath,testRuntimeClasspath
org.testcontainers:postgresql:1.20.1=testCompileClasspath,testRuntimeClasspath
org.testcontainers:testcontainers:1.20.1=testCompileClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.20.2=testCompileClasspath,testRuntimeClasspath
org.testcontainers:jdbc:1.20.2=testCompileClasspath,testRuntimeClasspath
org.testcontainers:junit-jupiter:1.20.2=testCompileClasspath,testRuntimeClasspath
org.testcontainers:postgresql:1.20.2=testCompileClasspath,testRuntimeClasspath
org.testcontainers:testcontainers:1.20.2=testCompileClasspath,testRuntimeClasspath
empty=implementationApi,schema
8 changes: 4 additions & 4 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ ext {
// TODO: Remove after the legacy console is deleted.
'com.google.closure-stylesheets:closure-stylesheets:1.5.0',
'com.google.javascript:closure-compiler:v20210505',
'org.seleniumhq.selenium:selenium-api:3.141.59',
'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59',
'org.seleniumhq.selenium:selenium-java:3.141.59',
'org.seleniumhq.selenium:selenium-remote-driver:3.141.59',
'org.seleniumhq.selenium:selenium-api:4.25.0',
'org.seleniumhq.selenium:selenium-chrome-driver:4.25.0',
'org.seleniumhq.selenium:selenium-java:4.25.0',
'org.seleniumhq.selenium:selenium-remote-driver:4.25.0',

// TODO: Migrate Soy Tofu to Soy Sauce (go/soysauce-migration)
'com.google.template:soy:2021-02-01',
Expand Down
Loading