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 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
18 changes: 16 additions & 2 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,26 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest

services:
hub:
image: selenium/hub:3.141.59-gold
ports:
- 4444:4444
firefox:
image: selenium/node-firefox:3.141.59-gold
env:
HUB_HOST: hub
HUB_PORT: 4444
chrome:
image: selenium/node-chrome:3.141.59-gold
env:
HUB_HOST: hub
HUB_PORT: 4444
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
run: JAVA_HOME=$JAVA_HOME_8_X64 mvn -V -B -ntp -U -e verify -Pwebdriver-tests -Dwebdriver.test.host=$(hostname)

44 changes: 44 additions & 0 deletions gwt-core-gwt2-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,48 @@
</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>
<webdriver.version>3.141.59</webdriver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-remote-driver</artifactId>
<version>${webdriver.version}</version>
</dependency>
</dependencies>
<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.RunStyleWebDriver: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,134 @@
/*
* 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.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import java.util.ArrayList;
import java.util.List;
import org.openqa.selenium.Platform;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

public class RunStyleWebDriver extends RunStyle {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That class should probably be named RunStyleRemoteWebDriver, as it doesn't support running "local" WebDriver.


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

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

@Override
public int initialize(String args) {
if (args == null || args.length() == 0) {
getLogger()
.log(
TreeLogger.ERROR,
"WebDriver runstyle requires a parameter of the form protocol://hostname:port?browser1[,browser2]");
return -1;
}
String[] parts = args.split("\\?");
URL remoteAddress = null;
try {
remoteAddress = new URL(parts[0] + "/wd/hub");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about letting the user put /wd/hub in the argument? (for forward compatibility)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm mistaken, /wd/hub is the default, and at least I personally haven't seen a different path used since webdriver first shipped (some 10 years ago).

Perhaps default to /wd/hub if no path is provided in the url before the ??

} catch (MalformedURLException e) {
getLogger().log(TreeLogger.ERROR, e.getMessage(), e);
return -1;
}

// build each driver based on parts[1].split(",")
String[] browserNames = parts[1].split(",");

for (String browserName : browserNames) {
DesiredCapabilities capabilities = new DesiredCapabilities(browserName, "", Platform.ANY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this possibly read from a JSON file to further configure capabilities? (e.g. allow running against Sauce Labs, etc.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have an example of what such a JSON would look like? I see that DesiredCapabilities can take a Map<String, ?>, but the values can be pretty arbitrary.

Proxy looks like the only property in the default impls that actually has any structure, and it accepts a Map as well, so perhaps it is safe to just deserialize JSON to a recursive map.

My main concern then is parameterizing this, passing a path when you just need a browser seems excessive, and passing a path as a string seems messy too (since runStyle can only take a single string, which you then parse yourself, etc).

What do you think of supporting multiple arg patterns, one which roughly matches the current setup (just specify URL and browsers), and another for a config file? Perhaps two concrete types, to allow specifying which format will be used by the runstyle name, but shared implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be possible to have a single run style that supports everything with, say something like

-runStyle "org.gwtproject.junit.RunStyleRemoteWebDriver:http://localhost:4444;firefox,chrome;/path/to/config.json"

(I used ; as a separator, but that could be | or whatever, or ? and # to make it look like a URL (not sure it's a good idea))
If the config file path is absent, then the browser names correspond to "standard" WebDriver browser names, with their default capabilities; otherwise they should match keys in the JSON object (there could be a fallback mechanism to use default capabilities for the browser name, not sure it's a good idea to mix things here). And in case there's a config file path, the browsers' list could be empty to test on all configured browsers (i.e. the browsers' list works as a filter on the JSON keys); that way you can configure many browsers but only actually run tests on a few of them.

But maybe it's still better to have separate run styles 🤷‍♂️

Disclosure: I don't know Selenium/WebDriver much and never actually used them; only saw several FLOSS projects using WebDriver (from JS) with such configurations as JSON (or JS objects).


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