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

Run tests in webdriver for CI, both firefox and chrome #14

Open
wants to merge 6 commits 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 30 additions & 7 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,35 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest

services:
hub:
image: selenium/hub:4.16.1-20231219
ports:
- "4442:4442"
- "4443:4443"
- "4444:4444"
firefox:
image: selenium/node-firefox:4.16.1-20231219
env:
SE_EVENT_BUS_HOST: hub
SE_EVENT_BUS_PUBLISH_PORT: 4442
SE_EVENT_BUS_SUBSCRIBE_PORT: 4443
chrome:
image: selenium/node-chrome:4.16.1-20231219
env:
SE_EVENT_BUS_HOST: hub
SE_EVENT_BUS_PUBLISH_PORT: 4442
SE_EVENT_BUS_SUBSCRIBE_PORT: 4443
steps:
- uses: actions/checkout@v2

# TODO: cache dependencies (taking into accounts: Maven plugins, snapshots, etc.)

- name: Build with Maven
run: JAVA_HOME=$JAVA_HOME_8_X64 mvn -V -B -ntp -U -e verify
- name: Checkout
uses: actions/checkout@v4
- name: Setup JDK 11
id: setup-java-11
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
cache: 'maven'
- name: Build and test with Maven
run: mvn -V -B -ntp -U -e verify -Pwebdriver-tests -Dwebdriver.test.host=$(hostname)

43 changes: 43 additions & 0 deletions gwt-core-gwt2-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
<artifactId>gwt-core</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>4.16.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -100,4 +107,40 @@
</plugin>
</plugins>
</build>

<profiles>
<!-- This profile will run the test in firefox and chrome instead of htmlunit. To use it,
first start a local selenium hub using the same version as specified below, and start
the desired browsers. Default properties below expect that the hub will be running on
localhost:4444, and that the browsers will be able to connect to localhost where this
maven process is running - if using docker to run those containers, you will need to
specify -Dwebdriver.test.host= to be the hostname that those containers can reach.
-->
<profile>
<id>webdriver-tests</id>
<properties>
<webdriver.test.host>localhost</webdriver.test.host>
<webdriver.hub.host>localhost</webdriver.hub.host>
<webdriver.hub.port>4444</webdriver.hub.port>
<webdriver.browsers>firefox,chrome</webdriver.browsers>
</properties>
<build>
<plugins>
<plugin>
<groupId>net.ltgt.gwt.maven</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<configuration>
<systemProperties>
<webdriver.test.host>${webdriver.test.host}</webdriver.test.host>
</systemProperties>
<testArgs>
<testArg>-runStyle</testArg>
<testArg>org.gwtproject.junit.RunStyleRemoteWebDriver:http://${webdriver.hub.host}:${webdriver.hub.port}?${webdriver.browsers}</testArg>
</testArgs>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/*
* Copyright © 2019 The GWT Project Authors
*
* 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 org.gwtproject.junit;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.junit.JUnitShell;
import com.google.gwt.junit.RunStyle;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public abstract class RunStyleAbstractRemoteWebDriver extends RunStyle {

public static class RemoteWebDriverConfiguration {
private String remoteWebDriverUrl;
private List<Map<String, ?>> browserCapabilities;

public String getRemoteWebDriverUrl() {
return remoteWebDriverUrl;
}

public void setRemoteWebDriverUrl(String remoteWebDriverUrl) {
this.remoteWebDriverUrl = remoteWebDriverUrl;
}

public List<Map<String, ?>> getBrowserCapabilities() {
return browserCapabilities;
}

public void setBrowserCapabilities(List<Map<String, ?>> browserCapabilities) {
this.browserCapabilities = browserCapabilities;
}
}

public class ConfigurationException extends Exception {}

private List<RemoteWebDriver> browsers = new ArrayList<>();
private Thread keepalive;

public RunStyleAbstractRemoteWebDriver(JUnitShell shell) {
super(shell);
}

/**
* Validates the arguments for the specific subclass, and creates a configuration that describes
* how to run the tests.
*
* @param args the command line argument string passed from JUnitShell
* @return the configuration to use when running these tests
*/
protected abstract RemoteWebDriverConfiguration readConfiguration(String args)
throws ConfigurationException;

@Override
public final int initialize(String args) {
final RemoteWebDriverConfiguration config;
try {
config = readConfiguration(args);
} catch (ConfigurationException failed) {
// log should already have details about what went wrong, we will just return the failure
// value
return -1;
}

final URL remoteAddress;
try {
remoteAddress = new URL(config.getRemoteWebDriverUrl());
} catch (MalformedURLException e) {
getLogger().log(TreeLogger.ERROR, e.getMessage(), e);
return -1;
}

for (Map<String, ?> capabilityMap : config.getBrowserCapabilities()) {
DesiredCapabilities capabilities = new DesiredCapabilities(capabilityMap);

try {
RemoteWebDriver wd = new RemoteWebDriver(remoteAddress, capabilities);
browsers.add(wd);
} catch (Exception exception) {
getLogger().log(TreeLogger.ERROR, "Failed to find desired browser", exception);
return -1;
}
}

Runtime.getRuntime()
.addShutdownHook(
new Thread(
() -> {
if (keepalive != null) {
keepalive.interrupt();
}
for (RemoteWebDriver browser : browsers) {
try {
browser.quit();
} catch (Exception ignored) {
// ignore, we're shutting down, continue shutting down others
}
}
}));
return browsers.size();
}

@Override
public void launchModule(String moduleName) throws UnableToCompleteException {
// since WebDriver.get is blocking, start a keepalive thread first
keepalive =
new Thread(
() -> {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
for (RemoteWebDriver browser : browsers) {
browser.getTitle(); // as in RunStyleSelenium, simple way to poll the browser
}
}
});
keepalive.setDaemon(true);
keepalive.start();
for (RemoteWebDriver browser : browsers) {
browser.get(shell.getModuleUrl(moduleName));
}
}

/**
* Work-around until GWT's JUnitShell handles IPv6 addresses correctly.
* https://groups.google.com/d/msg/google-web-toolkit/jLGhwUrKVRY/eQaDO6EUqdYJ
*/
public String getLocalHostName() {
String host = System.getProperty("webdriver.test.host");
if (host != null) {
return host;
}
InetAddress a;
try {
a = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
throw new RuntimeException("Unable to determine my ip address", e);
}
if (a instanceof Inet6Address) {
return "[" + a.getHostAddress() + "]";
} else {
return a.getHostAddress();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright © 2019 The GWT Project Authors
*
* 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 org.gwtproject.junit;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.junit.JUnitShell;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import org.openqa.selenium.remote.DesiredCapabilities;

public class RunStyleRemoteWebDriver extends RunStyleAbstractRemoteWebDriver {

public RunStyleRemoteWebDriver(JUnitShell shell) {
super(shell);
}

@Override
protected RemoteWebDriverConfiguration readConfiguration(String args)
throws ConfigurationException {
RemoteWebDriverConfiguration config = new RemoteWebDriverConfiguration();
if (args == null || args.length() == 0) {
getLogger()
.log(
TreeLogger.ERROR,
"RemoteWebDriver runstyle requires a parameter of the form protocol://hostname:port?browser1[,browser2]");
throw new ConfigurationException();
}

String[] parts = args.split("\\?");
String url = parts[0];
URL remoteAddress = null;
try {
remoteAddress = new URL(url);
if (remoteAddress.getPath().equals("")
|| (remoteAddress.getPath().equals("/") && !url.endsWith("/"))) {
getLogger()
.log(
TreeLogger.INFO,
"No path specified in webdriver remote url, using default of /wd/hub");
config.setRemoteWebDriverUrl(url + "/wd/hub");
} else {
config.setRemoteWebDriverUrl(url);
}
} catch (MalformedURLException e) {
getLogger().log(TreeLogger.ERROR, e.getMessage(), e);
throw new ConfigurationException();
}

// build each driver based on parts[1].split(",")
String[] browserNames = parts[1].split(",");
config.setBrowserCapabilities(new ArrayList<>());
for (String browserName : browserNames) {
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setBrowserName(browserName);
config.getBrowserCapabilities().add(capabilities.asMap());
}

return config;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright © 2019 The GWT Project Authors
*
* 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 org.gwtproject.junit;

import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonSyntaxException;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.junit.JUnitShell;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;

public class RunStyleRemoteWebDriverWithConfig extends RunStyleAbstractRemoteWebDriver {
public RunStyleRemoteWebDriverWithConfig(JUnitShell shell) {
super(shell);
}

@Override
protected RemoteWebDriverConfiguration readConfiguration(String args)
throws ConfigurationException {
File jsonConfigFile = new File(args);

try {
RemoteWebDriverConfiguration config =
new Gson().fromJson(new FileReader(jsonConfigFile), RemoteWebDriverConfiguration.class);
return config;
} catch (FileNotFoundException e) {
getLogger().log(TreeLogger.Type.ERROR, "Configuration file not found: " + args, e);
throw new ConfigurationException();
} catch (JsonIOException e) {
getLogger().log(TreeLogger.Type.ERROR, "Error reading config file: " + args, e);
throw new ConfigurationException();
} catch (JsonSyntaxException e) {
getLogger().log(TreeLogger.Type.ERROR, "Error parsing config file: " + args, e);
throw new ConfigurationException();
}
}
}