diff --git a/.travis.yml b/.travis.yml index b0df96c..f734424 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ +dist: bionic language: java -sudo: required +addons: + chrome: stable + firefox: latest +services: + - xvfb jdk: - openjdk8 - openjdk11 @@ -14,18 +19,18 @@ before_cache: before_install: - nvm i node install: - - npm install github:GoogleChromeLabs/web-push-testing-service -g + - npm i -g testcafe before_script: - - "export DISPLAY=:99.0" - - "sh -e /etc/init.d/xvfb start || echo \"Unable to start virtual display.\"" - - sleep 3 + - fluxbox >/dev/null 2>&1 & + - sleep 5 script: - - web-push-testing-service start wpts - ./gradlew clean check - - web-push-testing-service stop wpts + - ./gradlew :startE2eTestServer & + - sleep 5 + - xvfb-run -a testcafe 'chrome --user-data-dir $(pwd)/testcafe/chrome' testcafe/test.js --hostname localhost --ports 12345,54321 --skip-js-errors + - xvfb-run -a testcafe 'firefox -profile $(pwd)/testcafe/firefox' testcafe/test.js --hostname localhost --ports 12345,54321 cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - - ~/.selenium-assistant - node_modules diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b511c4..1995e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 5.2.0 + +* Maintenance: Upgrade Gradle to 6.5.1 +* Maintenance: Convert build script to use Kotlin DSL instead of Groovy DSL + # 5.1.0 * Improvement: Add support for [urgency](https://tools.ietf.org/html/rfc8030#section-5.3) & [topic](https://tools.ietf.org/html/rfc8030#section-5.4) (contributed by jamie@checkin.tech). diff --git a/README.md b/README.md index c990d47..d76ba8a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WebPush -A Web Push library for Java 7. Supports payloads and VAPID. +A Web Push library for Java 8. Supports payloads and VAPID. [![Build Status](https://travis-ci.org/web-push-libs/webpush-java.svg?branch=master)](https://travis-ci.org/web-push-libs/webpush-java) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/nl.martijndwars/web-push/badge.svg)](https://search.maven.org/search?q=g:nl.martijndwars%20AND%20a:web-push) @@ -123,28 +123,24 @@ for detailed usage instructions. If you plan on using VAPID, read [wiki/VAPID](h ## Testing -The integration tests use [Web Push Testing Service (WPTS)](https://github.com/GoogleChromeLabs/web-push-testing-service) to handle the Selenium and browser orchestrating. We use a forked version that fixes a bug on macOS. To install WPTS: +The e2e tests use testcafe. To install testcafe: ``` -npm i -g github:MartijnDwars/web-push-testing-service#bump-selenium-assistant +npm install -g testcafe ``` -Then start WPTS: +Then start the test server: ``` -web-push-testing-service start wpts +./gradlew :startE2eTestServer ``` -Then run the tests: +Run the testcafe tests: ``` -./gradlew clean test -``` - -Finally, stop WPTS: - -``` -web-push-testing-service stop wpts +#/usr/bin/env bash +testcafe 'chrome --user-data-dir $(pwd)/testcafe/chrome' testcafe/test.js --hostname localhost --ports 12345,54321 --skip-js-errors +testcafe 'firefox -profile $(pwd)/testcafe/firefox' testcafe/test.js --hostname localhost --ports 12345,54321 ``` ## FAQ diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 737f443..0000000 --- a/build.gradle +++ /dev/null @@ -1,103 +0,0 @@ -plugins { - id 'application' - id 'com.github.johnrengelman.shadow' version '5.2.0' - - // Used by release.gradle - id 'maven-publish' - id 'signing' - id 'io.codearte.nexus-staging' version '0.21.1' -} - -apply plugin: 'application' -apply plugin: 'com.github.johnrengelman.shadow' - -group 'nl.martijndwars' -version '5.1.1-SNAPSHOT' - -repositories { - mavenLocal() - mavenCentral() -} - -dependencies { - // For CLI - compile group: 'com.beust', name: 'jcommander', version: '1.78' - - // For making async HTTP requests - compile group: 'org.apache.httpcomponents', name: 'httpasyncclient', version: '4.1.4' - - // For cryptographic operations - shadow group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.64' - - // For creating and signing JWT - compile group: 'org.bitbucket.b_c', name: 'jose4j', version: '0.7.0' - - // For parsing JSON - testCompile group: 'com.google.code.gson', name: 'gson', version: '2.8.6' - - // For making HTTP requests - testCompile group: 'org.apache.httpcomponents', name: 'fluent-hc', version: '4.5.10' - - // For testing, obviously - testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.2' - - // For running JUnit tests - testRuntime group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.2' - - // For turning InputStream to String - testCompile group: 'commons-io', name: 'commons-io', version: '2.6' - - // For reading the demo vapid keypair from a pem file - testCompile group: 'org.bouncycastle', name: 'bcpkix-jdk15on', version: '1.64' - - // For verifying Base64Encoder results in unit tests - testCompile group: 'com.google.guava', name: 'guava', version: '27.0.1-jre' -} - -wrapper { - gradleVersion = '5.1' -} - -compileJava { - sourceCompatibility = 1.7 - targetCompatibility = 1.7 -} - -compileTestJava { - sourceCompatibility = 1.8 -} - -mainClassName = 'nl.martijndwars.webpush.cli.Cli' - -run { - classpath configurations.shadow.files -} - -test { - useJUnitPlatform() - - testLogging { - events 'PASSED', 'FAILED', 'SKIPPED' - showStandardStreams true - exceptionFormat 'full' - } -} - -task javadocJar(type: Jar) { - classifier = 'javadoc' - from javadoc -} - -task sourcesJar(type: Jar) { - classifier = 'sources' - from sourceSets.main.allSource -} - -artifacts { - archives javadocJar - archives sourcesJar -} - -if (hasProperty('release')) { - apply from: 'release.gradle' -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a52486d --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,94 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + +plugins { + application + id("com.github.johnrengelman.shadow") version "6.0.0" + + // Used by release.gradle.kts + `maven-publish` + signing + id("io.codearte.nexus-staging") version "0.21.1" +} + +group = "nl.martijndwars" +version = "5.1.1-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + // For CLI + implementation(group = "com.beust", name = "jcommander", version = "1.78") + + // For making async HTTP requests + implementation(group = "org.apache.httpcomponents", name = "httpasyncclient", version = "4.1.4") + + // For cryptographic operations + shadow(group = "org.bouncycastle", name = "bcprov-jdk15on", version = "1.64") + + // For creating and signing JWT + implementation(group = "org.bitbucket.b_c", name = "jose4j", version = "0.7.0") + + // For parsing JSON + testImplementation(group = "com.google.code.gson", name = "gson", version = "2.8.6") + + // For making HTTP requests + testImplementation(group = "org.apache.httpcomponents", name = "fluent-hc", version = "4.5.10") + + // For testing, obviously + testImplementation(group = "org.junit.jupiter", name = "junit-jupiter-api", version = "5.5.2") + + // For running JUnit tests + testRuntimeOnly(group = "org.junit.jupiter", name = "junit-jupiter-engine", version = "5.5.2") + + // For turning InputStream to String + testImplementation(group = "commons-io", name = "commons-io", version = "2.6") + + // For reading the demo vapid keypair from a pem file + testImplementation(group = "org.bouncycastle", name = "bcpkix-jdk15on", version = "1.64") + + // For verifying Base64Encoder results in unit tests + testImplementation(group = "com.google.guava", name = "guava", version = "27.0.1-jre") + + // To run a local webserver in the TestCafe tests + testImplementation(group = "io.undertow", name = "undertow-servlet", version = "2.1.3.Final") +} + +tasks.named("wrapper") { + gradleVersion = "6.5.1" +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +application { + mainClassName = "nl.martijndwars.webpush.cli.Cli" +} + +tasks.named("run") { + classpath += files(configurations.shadow) +} + +tasks.named("test") { + useJUnitPlatform() + testLogging { + events("PASSED", "FAILED", "SKIPPED") + showStandardStreams = true + exceptionFormat = FULL + } +} + +val startE2eTestServer = task("startE2eTestServer") { + description = "Start e2e test server." + classpath = sourceSets["test"].runtimeClasspath + main = "nl.martijndwars.webpush.Webserver" +} + +if (hasProperty("release")) { + apply { + from("release.gradle.kts") + } +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 457aad0..cc4fdc2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9492014..bb8b2fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index af6708f..2fe81a7 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or 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 +# +# https://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. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -109,8 +125,8 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` @@ -138,19 +154,19 @@ if $cygwin ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -159,14 +175,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 0f8d593..24467a1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/release.gradle b/release.gradle deleted file mode 100644 index 4c09f1d..0000000 --- a/release.gradle +++ /dev/null @@ -1,54 +0,0 @@ -publishing { - publications { - mavenJava(MavenPublication) { - from components.java - artifact sourcesJar - artifact javadocJar - pom { - name = 'web-push' - description = 'A Web Push library for Java.' - url = 'https://github.com/web-push-libs/webpush-java' - scm { - connection = 'scm:git:git@github.com:web-push-libs/webpush-java.git' - developerConnection = 'scm:git:git@github.com:web-push-libs/webpush-java.git' - url = 'git@github.com:web-push-libs/webpush-java.git' - } - licenses { - license { - name = 'MIT License' - url = 'https://opensource.org/licenses/MIT' - } - } - developers { - developer { - id = 'martijndwars' - name = 'Martijn Dwars' - email = 'ikben@martijndwars.nl' - } - } - } - - } - } - repositories { - maven { - credentials { - username ossrhUsername - password ossrhPassword - } - url getRepositoryUrl() - } - } -} - -signing { - sign publishing.publications.mavenJava -} - -def getRepositoryUrl() { - if (version.endsWith('SNAPSHOT')) { - return 'https://oss.sonatype.org/content/repositories/snapshots/' - } else { - return 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' - } -} diff --git a/release.gradle.kts b/release.gradle.kts new file mode 100644 index 0000000..aee1065 --- /dev/null +++ b/release.gradle.kts @@ -0,0 +1,54 @@ +val ossrhUsername: String by project +val ossrhPassword: String by project + +publishing { + publications { + create("mavenJava") { + from(components["java"]) + pom { + name.set("web-push") + description.set("A Web Push library for Java.") + url.set("https://github.com/web-push-libs/webpush-java") + scm { + connection.set("scm:git:git@github.com:web-push-libs/webpush-java.git") + developerConnection.set("scm:git:git@github.com:web-push-libs/webpush-java.git") + url.set("git@github.com:web-push-libs/webpush-java.git") + } + licenses { + license { + name.set("MIT License") + url.set("https://opensource.org/licenses/MIT") + } + } + developers { + developer { + id.set("martijndwars") + name.set("Martijn Dwars") + email.set("ikben@martijndwars.nl") + } + } + } + } + } + repositories { + maven { + credentials { + username = ossrhUsername + password = ossrhPassword + } + url = uri(getRepository()) + } + } +} + +signing { + sign(publishing.publications["mavenJava"]) +} + +fun getRepository() { + if (version.toString().endsWith("SNAPSHOT")) { + "https://oss.sonatype.org/content/repositories/snapshots/" + } else { + "https://oss.sonatype.org/service/local/staging/deploy/maven2/" + } +} diff --git a/src/test/java/nl/martijndwars/webpush/Webserver.java b/src/test/java/nl/martijndwars/webpush/Webserver.java new file mode 100644 index 0000000..d2af4bd --- /dev/null +++ b/src/test/java/nl/martijndwars/webpush/Webserver.java @@ -0,0 +1,79 @@ +package nl.martijndwars.webpush; + +import java.nio.file.Paths; +import java.security.Security; + +import com.google.gson.Gson; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.predicate.Predicates; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.form.FormData; +import io.undertow.server.handlers.form.FormDataParser; +import io.undertow.server.handlers.form.FormParserFactory; +import io.undertow.server.handlers.resource.PathResourceManager; +import org.apache.http.HttpResponse; +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +public class Webserver { + protected final Undertow server; + + public static void main(String[] args) throws InterruptedException { + Webserver webserver = new Webserver(); + webserver.start(); + + Thread.sleep(600 * 1000); + } + + public Webserver() { + this.server = Undertow.builder() + .addHttpListener(5000, "localhost") + .setHandler(getHandler()) + .build(); + } + + public void start() { + server.start(); + } + + protected HttpHandler getHandler() { + return Handlers.predicate( + Predicates.suffixes(".html", ".js"), + Handlers.resource(new PathResourceManager(Paths.get("src/test/resources/static"))), + Handlers + .path(Handlers.redirect("/index.html")) + .addPrefixPath("/send", getSendHandler()) + ); + } + + protected HttpHandler getSendHandler() { + return exchange -> { + FormDataParser parser = FormParserFactory.builder().build().createParser(exchange); + MyHandler handler = new MyHandler(); + parser.parse(handler); + }; + } + + private static class MyHandler implements HttpHandler{ + final String PUBLIC_KEY = "BAPGG2IY3Vn48d_H8QNuVLRErkBI0L7oDOOCAMUBqYMTMTzukaIAuB5OOcmkdeRICcyQocEwD-oxVc81YXXZPRY"; + final String PRIVATE_KEY = "A7xDGuxMZ4ufflcAhBW23xpoWZNOLwM4Rw2wXjP0y6M"; + final String SUBJECT = "Foobarbaz"; + final String PAYLOAD = "My fancy message"; + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + FormData formData = exchange.getAttachment(FormDataParser.FORM_DATA); + String subscriptionJson = formData.get("subscriptionJson").getFirst().getValue(); + System.out.println(subscriptionJson); + + Security.addProvider(new BouncyCastleProvider()); + PushService pushService = new PushService(PUBLIC_KEY, PRIVATE_KEY, SUBJECT); + Subscription subscription = new Gson().fromJson(subscriptionJson, Subscription.class); + Notification notification = new Notification(subscription, PAYLOAD); + HttpResponse httpResponse = pushService.send(notification); + int statusCode = httpResponse.getStatusLine().getStatusCode(); + System.out.println(statusCode); + } + } +} diff --git a/src/test/java/nl/martijndwars/webpush/selenium/BrowserTest.java b/src/test/java/nl/martijndwars/webpush/selenium/BrowserTest.java deleted file mode 100644 index 37ed6d2..0000000 --- a/src/test/java/nl/martijndwars/webpush/selenium/BrowserTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package nl.martijndwars.webpush.selenium; - -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; -import nl.martijndwars.webpush.Notification; -import nl.martijndwars.webpush.PushService; -import nl.martijndwars.webpush.Subscription; -import org.apache.http.HttpResponse; -import org.junit.jupiter.api.function.Executable; - -import java.security.GeneralSecurityException; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class BrowserTest implements Executable { - public static final String GCM_API_KEY = "AIzaSyBAU0VfXoskxUSg81K5VgLgwblHbZWe6tA"; - public static final String PUBLIC_KEY = "BNFDO1MUnNpx0SuQyQcAAWYETa2+W8z/uc5sxByf/UZLHwAhFLwEDxS5iB654KHiryq0AxDhFXS7DVqXDKjjN+8="; - public static final String PRIVATE_KEY = "AM0aAyoIryzARADnIsSCwg1p1aWFAL3Idc8dNXpf74MH"; - public static final String VAPID_SUBJECT = "http://localhost:8090"; - - private TestingService testingService; - private Configuration configuration; - private int testSuiteId; - - public BrowserTest(TestingService testingService, Configuration configuration, int testSuiteId) { - this.configuration = configuration; - this.testingService = testingService; - this.testSuiteId = testSuiteId; - } - - /** - * Execute the test for the given browser configuration. - * - * @throws Throwable - */ - @Override - public void execute() throws Throwable { - PushService pushService = getPushService(); - - JsonObject test = testingService.getSubscription(testSuiteId, configuration); - - int testId = test.get("testId").getAsInt(); - - Subscription subscription = new Gson().fromJson(test.get("subscription").getAsJsonObject(), Subscription.class); - - String message = "Hëllö, world!"; - Notification notification = new Notification(subscription, message); - - HttpResponse response = pushService.send(notification); - assertEquals(201, response.getStatusLine().getStatusCode()); - - JsonArray messages = testingService.getNotificationStatus(testSuiteId, testId); - assertEquals(1, messages.size()); - assertEquals(new JsonPrimitive(message), messages.get(0)); - } - - protected PushService getPushService() throws GeneralSecurityException { - PushService pushService; - - if (!configuration.isVapid()) { - pushService = new PushService(GCM_API_KEY); - } else { - pushService = new PushService(PUBLIC_KEY, PRIVATE_KEY, VAPID_SUBJECT); - } - return pushService; - } - - /** - * The name used by JUnit to display the test. - * - * @return - */ - public String getDisplayName() { - return "Browser " + configuration.browser + ", version " + configuration.version + ", vapid " + configuration.isVapid(); - } -} diff --git a/src/test/java/nl/martijndwars/webpush/selenium/Configuration.java b/src/test/java/nl/martijndwars/webpush/selenium/Configuration.java deleted file mode 100644 index 5b4e4e1..0000000 --- a/src/test/java/nl/martijndwars/webpush/selenium/Configuration.java +++ /dev/null @@ -1,24 +0,0 @@ -package nl.martijndwars.webpush.selenium; - -public class Configuration { - protected final String browser; - protected final String version; - protected final String publicKey; - protected final String gcmSenderId; - - Configuration(String browser, String version, String publicKey, String gcmSenderId) { - this.browser = browser; - this.version = version; - this.publicKey = publicKey; - this.gcmSenderId = gcmSenderId; - } - - public boolean isVapid() { - return publicKey != null && !publicKey.isEmpty(); - } - - @Override - public String toString() { - return browser + ", " + version + ", " + publicKey; - } -} diff --git a/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java b/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java deleted file mode 100644 index de1353e..0000000 --- a/src/test/java/nl/martijndwars/webpush/selenium/SeleniumTests.java +++ /dev/null @@ -1,81 +0,0 @@ -package nl.martijndwars.webpush.selenium; - -import nl.martijndwars.webpush.Base64Encoder; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.TestFactory; - -import java.io.IOException; -import java.security.Security; -import java.util.stream.Stream; - -import static org.junit.jupiter.api.DynamicTest.dynamicTest; - -/** - * SeleniumTest performs integration testing. - */ -public class SeleniumTests { - protected static final String GCM_SENDER_ID = "759071690750"; - protected static final String PUBLIC_KEY = "BNFDO1MUnNpx0SuQyQcAAWYETa2+W8z/uc5sxByf/UZLHwAhFLwEDxS5iB654KHiryq0AxDhFXS7DVqXDKjjN+8="; - - protected static TestingService testingService = new TestingService("http://localhost:8090/api/"); - protected static int testSuiteId; - - public SeleniumTests() { - Security.addProvider(new BouncyCastleProvider()); - } - - /** - * End the test suite. - * - * @throws IOException - */ - @AfterAll - public static void tearDown() throws IOException { - testingService.endTestSuite(testSuiteId); - } - - /** - * Generate a stream of tests based on the configurations. - * - * @return - */ - @TestFactory - public Stream dynamicTests() throws IOException { - testSuiteId = testingService.startTestSuite(); - - return getConfigurations().map(configuration -> { - BrowserTest browserTest = new BrowserTest(testingService, configuration, testSuiteId); - - return dynamicTest(browserTest.getDisplayName(), browserTest); - }); - } - - /** - * Get browser configurations to test. - * - * @return - */ - protected Stream getConfigurations() { - String PUBLIC_KEY_NO_PADDING = Base64Encoder.encodeWithoutPadding( - Base64Encoder.decode(PUBLIC_KEY) - ); - - return Stream.of( - new Configuration("chrome", "stable", null, GCM_SENDER_ID), - new Configuration("chrome", "beta", null, GCM_SENDER_ID), - //new Configuration("chrome", "unstable", null, GCM_SENDER_ID), See #90 - - new Configuration("firefox", "stable", null, GCM_SENDER_ID), - new Configuration("firefox", "beta", null, GCM_SENDER_ID), - - new Configuration("chrome", "stable", PUBLIC_KEY_NO_PADDING, null), - new Configuration("chrome", "beta", PUBLIC_KEY_NO_PADDING, null), - //new Configuration("chrome", "unstable", PUBLIC_KEY_NO_PADDING, null), See #90 - - new Configuration("firefox", "stable", PUBLIC_KEY_NO_PADDING, null), - new Configuration("firefox", "beta", PUBLIC_KEY_NO_PADDING, null) - ); - } -} diff --git a/src/test/java/nl/martijndwars/webpush/selenium/TestingService.java b/src/test/java/nl/martijndwars/webpush/selenium/TestingService.java deleted file mode 100644 index a89f9a9..0000000 --- a/src/test/java/nl/martijndwars/webpush/selenium/TestingService.java +++ /dev/null @@ -1,160 +0,0 @@ -package nl.martijndwars.webpush.selenium; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.client.fluent.Request; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.util.EntityUtils; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Java wrapper for interacting with the Web Push Testing Service. - */ -public class TestingService { - private String baseUrl; - - public TestingService(String baseUrl) { - this.baseUrl = baseUrl; - } - - /** - * Start a new test suite. - * - * @return - */ - public int startTestSuite() throws IOException { - String startTestSuite = request(baseUrl + "start-test-suite/"); - - JsonElement root = JsonParser.parseString(startTestSuite); - - return root - .getAsJsonObject() - .get("data") - .getAsJsonObject() - .get("testSuiteId") - .getAsInt(); - } - - /** - * Get a test ID and subscription for the given test case. - * - * @param testSuiteId - * @param configuration - * @return - * @throws IOException - */ - public JsonObject getSubscription(int testSuiteId, Configuration configuration) throws IOException { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("testSuiteId", testSuiteId); - jsonObject.addProperty("browserName", configuration.browser); - jsonObject.addProperty("browserVersion", configuration.version); - - if (configuration.gcmSenderId != null) { - jsonObject.addProperty("gcmSenderId", configuration.gcmSenderId); - } - - if (configuration.publicKey != null) { - jsonObject.addProperty("vapidPublicKey", configuration.publicKey); - } - - HttpEntity entity = new StringEntity(jsonObject.toString(), ContentType.APPLICATION_JSON); - - String getSubscription = request(baseUrl + "get-subscription/", entity); - - return getData(getSubscription); - } - - /** - * Get the notification status for the given test case. - * - * @param testSuiteId - * @param testId - * @return - * @throws IOException - */ - public JsonArray getNotificationStatus(int testSuiteId, int testId) throws IOException { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("testSuiteId", testSuiteId); - jsonObject.addProperty("testId", testId); - - HttpEntity entity = new StringEntity(jsonObject.toString(), ContentType.APPLICATION_JSON); - - String notificationStatus = request(baseUrl + "get-notification-status/", entity); - - return getData(notificationStatus).get("messages").getAsJsonArray(); - } - - /** - * End the given test suite. - * - * @return - */ - public boolean endTestSuite(int testSuiteId) throws IOException { - JsonObject jsonObject = new JsonObject(); - jsonObject.addProperty("testSuiteId", testSuiteId); - - HttpEntity entity = new StringEntity(jsonObject.toString(), ContentType.APPLICATION_JSON); - - String endTestSuite = request(baseUrl + "end-test-suite/", entity); - - return getData(endTestSuite).get("success").getAsBoolean(); - } - - /** - * Perform HTTP request and return response. - * - * @param uri - * @return - */ - protected String request(String uri) throws IOException { - return request(uri, null); - } - - /** - * Perform HTTP request and return response. - * - * @param uri - * @return - */ - protected String request(String uri, HttpEntity entity) throws IOException { - return Request.Post(uri).body(entity).execute().handleResponse(httpResponse -> { - String json = EntityUtils.toString(httpResponse.getEntity()); - - if (httpResponse.getStatusLine().getStatusCode() != 200) { - JsonElement root = JsonParser.parseString(json); - JsonObject error = root.getAsJsonObject().get("error").getAsJsonObject(); - - String errorId = error.get("id").getAsString(); - String errorMessage = error.get("message").getAsString(); - - String body = IOUtils.toString(entity.getContent(), UTF_8); - - throw new IllegalStateException("Error while requesting " + uri + " with body " + body + " (" + errorId + ": " + errorMessage); - } - - return json; - }); - } - - /** - * Get the a JSON object of the data in the JSON response. - * - * @param response - */ - protected JsonObject getData(String response) { - JsonElement root = JsonParser.parseString(response); - - return root - .getAsJsonObject() - .get("data") - .getAsJsonObject(); - } -} diff --git a/src/test/resources/static/index.html b/src/test/resources/static/index.html new file mode 100644 index 0000000..48fdae0 --- /dev/null +++ b/src/test/resources/static/index.html @@ -0,0 +1,48 @@ + + + + + + +

Step 1

+ +
+
Service worker supported?
+
+ +
Show notification supported?
+
+ +
Notification permission?
+
+ +
Push Manager?
+
+ +
Service worker ready?
+
+ +
Subscription ready?
+
+ +
Service worker ready 2?
+
+ +
Subscription ready 2?
+
+
+ +

Step 2

+ + Subscription: + +

Step 2

+ + + +

Step 3

+ +
    +
+ + diff --git a/src/test/resources/static/push.js b/src/test/resources/static/push.js new file mode 100644 index 0000000..08b9a7a --- /dev/null +++ b/src/test/resources/static/push.js @@ -0,0 +1,148 @@ +window.addEventListener('load', registerServiceWorker, false); + +function registerServiceWorker() { + if ('serviceWorker' in navigator) { + document.getElementById('service-worker').append('y'); + navigator.serviceWorker.register('/sw.js').then(initialiseState); + } else { + document.getElementById('service-worker').append('n'); + console.warn('Service workers are not supported in this browser.'); + } +} + +function initialiseState() { + console.log('Service worker is registered.'); + + if (!('showNotification' in ServiceWorkerRegistration.prototype)) { + console.warn('Notifications aren\'t supported.'); + document.getElementById('show-notification').append('n'); + return; + } else { + document.getElementById('show-notification').append('y'); + } + + if (Notification.permission === 'denied') { + console.warn('The user has blocked notifications.'); + document.getElementById('notification-permission').append('n'); + return; + } else { + document.getElementById('notification-permission').append('y'); + } + + if (!('PushManager' in window)) { + console.warn('Push messaging isn\'t supported.'); + document.getElementById('push-manager').append('n'); + return; + } else { + document.getElementById('push-manager').append('y'); + } + + //var readyPromise = navigator.serviceWorker.ready; + var readyPromise = navigator.serviceWorker.getRegistration('./'); + readyPromise.then(function (serviceWorkerRegistration) { + console.log('Service worker is ready.'); + document.getElementById('service-worker-ready').append('y'); + + serviceWorkerRegistration.pushManager.getSubscription().then(function (subscription) { + console.log('Got subscription'); + console.log(subscription); + + if (!subscription) { + subscribe().then(function () { + document.getElementById('subscription-ready').append('y'); + }); + } else { + sendSubscriptionToServer(subscription); + document.getElementById('subscription-ready').append('y'); + } + }) + .catch(function (err) { + console.warn('Error during getSubscription()', err); + }); + }); +} + +function subscribe() { + const publicKey = base64UrlToUint8Array('BAPGG2IY3Vn48d_H8QNuVLRErkBI0L7oDOOCAMUBqYMTMTzukaIAuB5OOcmkdeRICcyQocEwD-oxVc81YXXZPRY'); + const subscribeOptions = { + userVisibleOnly: true, + applicationServerKey: publicKey + }; + + //var readyPromise = navigator.serviceWorker.ready; + var readyPromise = navigator.serviceWorker.getRegistration('./'); + + return readyPromise.then(function (serviceWorkerRegistration) { + document.getElementById('service-worker-ready-2').append('y'); + + return serviceWorkerRegistration.pushManager.subscribe(subscribeOptions).then(function (subscription) { + document.getElementById('subscription-ready').append('y'); + document.getElementById('subscription-ready-2').append('y'); + + return sendSubscriptionToServer(subscription); + }).catch(function (e) { + if (Notification.permission === 'denied') { + console.warn('Permission for Notifications was denied'); + document.getElementById('subscription-ready-2').append('Permission denied'); + } else { + console.error('Unable to subscribe to push.', e); + document.getElementById('subscription-ready-2').append('Unable to subscribe: ' + e); + } + }); + }); +} + +function sendSubscriptionToServer(subscription) { + var key = subscription.getKey ? subscription.getKey('p256dh') : ''; + var auth = subscription.getKey ? subscription.getKey('auth') : ''; + + document.getElementById('subscription').value = JSON.stringify(subscription); + + console.log({ + endpoint: subscription.endpoint, + key: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '', + auth: auth ? btoa(String.fromCharCode.apply(null, new Uint8Array(auth))) : '' + }); + + return Promise.resolve(); +} + +function base64UrlToUint8Array(base64UrlData) { + const padding = '='.repeat((4 - base64UrlData.length % 4) % 4); + const base64 = (base64UrlData + padding) + .replace(/\-/g, '+') + .replace(/_/g, '/'); + + const rawData = atob(base64); + const buffer = new Uint8Array(rawData.length); + + for (let i = 0; i < rawData.length; ++i) { + buffer[i] = rawData.charCodeAt(i); + } + + return buffer; +} + +window.addEventListener('load', function () { + var button = document.getElementById('send'); + button.addEventListener('click', function () { + var subscription = document.getElementById('subscription').value; + + let formData = new FormData(); + formData.append('subscriptionJson', subscription); + + fetch('/send', { + method: 'POST', + body: formData + }); + }); +}); + +// Use Broadcast API to listen for messages from the service worker +var broadcast = new BroadcastChannel('message-received'); + +broadcast.onmessage = function (event) { + var li = document.createElement('li'); + li.innerText = event.data.text; + document.getElementById('messages').append(li); +}; diff --git a/src/test/resources/static/sw.js b/src/test/resources/static/sw.js new file mode 100644 index 0000000..1d0d937 --- /dev/null +++ b/src/test/resources/static/sw.js @@ -0,0 +1,53 @@ +/** + * Service worker file. + * + * NOTE: This file MUST be located in the root. + */ + +'use strict'; + +var broadcast = new BroadcastChannel('message-received'); + +console.log('Started', self); + +self.addEventListener('install', function (event) { + self.skipWaiting(); + console.log('Installed', event); +}); + +self.addEventListener('activate', function (event) { + console.log('Activated', event); +}); + +self.addEventListener('push', function (event) { + console.log('Push message', event); + + var data = event.data.text(); + console.log('Push data: ' + data); + + // Broadcast message to the web page + broadcast.postMessage({ + text: event.data.text() + }); + + if (isJson(data)) { + var title = data.title; + var message = data.message; + } else { + var title = 'Push Message'; + var message = data; + } + + return self.registration.showNotification(title, { + body: 'Hello' + }); +}); + +var isJson = function (str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +} diff --git a/testcafe/chrome/.gitignore b/testcafe/chrome/.gitignore new file mode 100644 index 0000000..9b579ca --- /dev/null +++ b/testcafe/chrome/.gitignore @@ -0,0 +1,3 @@ +* +!Default +!.gitignore diff --git a/testcafe/chrome/Default/Preferences b/testcafe/chrome/Default/Preferences new file mode 100644 index 0000000..ac33640 --- /dev/null +++ b/testcafe/chrome/Default/Preferences @@ -0,0 +1,13 @@ +{ + "profile": { + "content_settings": { + "exceptions": { + "notifications": { + "http://localhost:12345,*": { + "setting": 1 + } + } + } + } + } +} diff --git a/testcafe/firefox/.gitignore b/testcafe/firefox/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/testcafe/firefox/.gitignore @@ -0,0 +1 @@ +* diff --git a/testcafe/firefox/AlternateServices.txt b/testcafe/firefox/AlternateServices.txt new file mode 100644 index 0000000..e69de29 diff --git a/testcafe/firefox/OfflineCache/index.sqlite b/testcafe/firefox/OfflineCache/index.sqlite new file mode 100644 index 0000000..7d7dadd Binary files /dev/null and b/testcafe/firefox/OfflineCache/index.sqlite differ diff --git a/testcafe/firefox/SecurityPreloadState.txt b/testcafe/firefox/SecurityPreloadState.txt new file mode 100644 index 0000000..e69de29 diff --git a/testcafe/firefox/SiteSecurityServiceState.txt b/testcafe/firefox/SiteSecurityServiceState.txt new file mode 100644 index 0000000..1e05d9a --- /dev/null +++ b/testcafe/firefox/SiteSecurityServiceState.txt @@ -0,0 +1,15 @@ +accounts.firefox.com:HSTS 0 18450 1625660241654,1,1,2 +search.services.mozilla.com:HSTS 0 18450 1625660802404,1,0,2 +www.google.com^firstPartyDomain=google-b-d.search.suggestions.mozilla:HSTS 0 18450 1625659926427,1,0,2 +firefox.settings.services.mozilla.com:HSTS 0 18450 1625660802647,1,0,2 +www.mozilla.org:HSTS 0 18450 1625659925569,1,0,2 +incoming.telemetry.mozilla.org:HSTS 0 18450 1609892593922,1,0,2 +aus5.mozilla.org:HSTS 0 18450 1625660374966,1,0,2 +services.addons.mozilla.org:HSTS 0 18450 1625660374705,1,0,2 +snippets.cdn.mozilla.net:HSTS 0 18450 1625659925331,1,0,2 +mozilla.org:HSTS 0 18450 1625659658691,1,0,2 +location.services.mozilla.com:HSTS 0 18450 1625659657613,1,1,2 +shavar.services.mozilla.com^firstPartyDomain=safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla:HSTS 0 18450 1625659658537,1,1,2 +normandy.cdn.mozilla.net:HSTS 0 18450 1625659925879,1,0,2 +www.google-analytics.com:HSTS 0 18450 1605010325746,1,1,2 +www.googletagmanager.com:HSTS 0 18450 1625659925431,1,1,2 diff --git a/testcafe/firefox/TRRBlacklist.txt b/testcafe/firefox/TRRBlacklist.txt new file mode 100644 index 0000000..e69de29 diff --git a/testcafe/firefox/cert9.db b/testcafe/firefox/cert9.db new file mode 100644 index 0000000..e69de29 diff --git a/testcafe/firefox/cert9.db-journal b/testcafe/firefox/cert9.db-journal new file mode 100644 index 0000000..019d291 Binary files /dev/null and b/testcafe/firefox/cert9.db-journal differ diff --git a/testcafe/firefox/datareporting/archived/2020-07/1594124856200.b9addc63-b929-0043-bb2b-a74e26bda56f.new-profile.jsonlz4 b/testcafe/firefox/datareporting/archived/2020-07/1594124856200.b9addc63-b929-0043-bb2b-a74e26bda56f.new-profile.jsonlz4 new file mode 100644 index 0000000..770a12a Binary files /dev/null and b/testcafe/firefox/datareporting/archived/2020-07/1594124856200.b9addc63-b929-0043-bb2b-a74e26bda56f.new-profile.jsonlz4 differ diff --git a/testcafe/firefox/datareporting/archived/2020-07/1594124856214.01b03e55-20ff-f04f-ad69-313d9f038d3c.main.jsonlz4 b/testcafe/firefox/datareporting/archived/2020-07/1594124856214.01b03e55-20ff-f04f-ad69-313d9f038d3c.main.jsonlz4 new file mode 100644 index 0000000..1e55717 Binary files /dev/null and b/testcafe/firefox/datareporting/archived/2020-07/1594124856214.01b03e55-20ff-f04f-ad69-313d9f038d3c.main.jsonlz4 differ diff --git a/testcafe/firefox/datareporting/session-state.json b/testcafe/firefox/datareporting/session-state.json new file mode 100644 index 0000000..bf7e6ce --- /dev/null +++ b/testcafe/firefox/datareporting/session-state.json @@ -0,0 +1 @@ +{"sessionId":"fe97f91f-19a8-554f-abae-5fc29a5a5649","subsessionId":"ef261319-79d2-294f-88ee-9e2c697fc4bd","profileSubsessionCounter":1,"newProfilePingSent":true} \ No newline at end of file diff --git a/testcafe/firefox/prefs.js b/testcafe/firefox/prefs.js new file mode 100644 index 0000000..3617faf --- /dev/null +++ b/testcafe/firefox/prefs.js @@ -0,0 +1,110 @@ +// Mozilla User Preferences + +// DO NOT EDIT THIS FILE. +// +// If you make changes to this file while the application is running, +// the changes will be overwritten when the application exits. +// +// To change a preference value, you can either: +// - modify it via the UI (e.g. via about:config in the browser); or +// - set it within a user.js file in your profile. + +user_pref("app.normandy.first_run", false); +user_pref("app.normandy.migrationsApplied", 10); +user_pref("app.normandy.startupExperimentPrefs.browser.search.param.google_channel_row", "trow2"); +user_pref("app.normandy.startupExperimentPrefs.browser.search.param.google_channel_us", "tus2"); +user_pref("app.normandy.startupExperimentPrefs.privacy.annotate_channels.strict_list.enabled", true); +user_pref("app.normandy.startupExperimentPrefs.urlclassifier.trackingAnnotationTable.testEntries", "etp-experiment-1.dummytracker.org"); +user_pref("app.normandy.user_id", "1602d62b-f72b-5f4d-8803-af862a569098"); +user_pref("app.update.download.attempts", 0); +user_pref("app.update.elevate.attempts", 0); +user_pref("app.update.lastUpdateTime.addon-background-update-timer", 1594124374); +user_pref("app.update.lastUpdateTime.background-update-timer", 1594124174); +user_pref("app.update.lastUpdateTime.browser-cleanup-thumbnails", 1594123686); +user_pref("app.update.lastUpdateTime.recipe-client-addon-run", 1594123928); +user_pref("app.update.lastUpdateTime.rs-experiment-loader-timer", 1594124146); +user_pref("app.update.lastUpdateTime.search-engine-update-timer", 1594123806); +user_pref("app.update.lastUpdateTime.services-settings-poll-changes", 1594124294); +user_pref("app.update.lastUpdateTime.telemetry_modules_ping", 1594123716); +user_pref("app.update.lastUpdateTime.xpi-signature-verification", 1594124494); +user_pref("browser.aboutConfig.showWarning", false); +user_pref("browser.bookmarks.restore_default_bookmarks", false); +user_pref("browser.cache.disk.amount_written", 28056); +user_pref("browser.cache.disk.capacity", 1048576); +user_pref("browser.cache.disk.filesystem_reported", 1); +user_pref("browser.contentblocking.category", "standard"); +user_pref("browser.laterrun.bookkeeping.profileCreationTime", 1594123656); +user_pref("browser.laterrun.bookkeeping.sessionCount", 6); +user_pref("browser.laterrun.enabled", true); +user_pref("browser.migration.version", 96); +user_pref("browser.newtabpage.activity-stream.impressionId", "{84004f26-fc86-b244-9d65-019bc2261911}"); +user_pref("browser.newtabpage.storageVersion", 1); +user_pref("browser.pageActions.persistedActions", "{\"version\":1,\"ids\":[\"bookmark\",\"pinTab\",\"bookmarkSeparator\",\"copyURL\",\"emailLink\",\"addSearchEngine\",\"sendToDevice\",\"shareURL\",\"pocket\"],\"idsInUrlbar\":[\"pocket\",\"bookmark\"]}"); +user_pref("browser.pagethumbnails.storage_version", 3); +user_pref("browser.rights.3.shown", true); +user_pref("browser.safebrowsing.provider.google4.lastupdatetime", "1594123687351"); +user_pref("browser.safebrowsing.provider.google4.nextupdatetime", "1594125465351"); +user_pref("browser.safebrowsing.provider.mozilla.lastupdatetime", "1594123662114"); +user_pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1594127262114"); +user_pref("browser.search.cohort", "nov17-1"); +user_pref("browser.search.region", "CH"); +user_pref("browser.sessionstore.upgradeBackup.latestBuildID", "20200630195452"); +user_pref("browser.shell.checkDefaultBrowser", false); +user_pref("browser.shell.didSkipDefaultBrowserCheckOnFirstRun", true); +user_pref("browser.slowStartup.averageTime", 1423); +user_pref("browser.slowStartup.samples", 1); +user_pref("browser.startup.homepage_override.buildID", "20200630195452"); +user_pref("browser.startup.homepage_override.mstone", "78.0.1"); +user_pref("browser.startup.lastColdStartupCheck", 1594124802); +user_pref("browser.tabs.warnOnClose", false); +user_pref("browser.uiCustomization.state", "{\"placements\":{\"widget-overflow-fixed-list\":[],\"nav-bar\":[\"back-button\",\"forward-button\",\"stop-reload-button\",\"home-button\",\"customizableui-special-spring1\",\"urlbar-container\",\"customizableui-special-spring2\",\"downloads-button\",\"library-button\",\"sidebar-button\",\"fxa-toolbar-menu-button\"],\"TabsToolbar\":[\"tabbrowser-tabs\",\"new-tab-button\",\"alltabs-button\"],\"PersonalToolbar\":[\"personal-bookmarks\"]},\"seen\":[\"developer-button\"],\"dirtyAreaCache\":[\"nav-bar\",\"TabsToolbar\",\"PersonalToolbar\"],\"currentVersion\":16,\"newElementCount\":2}"); +user_pref("browser.urlbar.placeholderName", "Google"); +user_pref("browser.urlbar.tipShownCount.searchTip_onboard", 4); +user_pref("datareporting.policy.dataSubmissionPolicyAcceptedVersion", 2); +user_pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "1594123658702"); +user_pref("devtools.debugger.pending-selected-location", "{\"sourceId\":\"sourceURL-http://localhost:12345/2CyNTtFfs!s!iso-8859-1/http://localhost:5000/push.js\",\"line\":94,\"column\":4,\"sourceUrl\":\"\",\"url\":\"http://localhost:12345/2CyNTtFfs!s!iso-8859-1/http://localhost:5000/push.js\"}"); +user_pref("devtools.debugger.prefs-schema-version", 11); +user_pref("devtools.toolbox.selectedTool", "webconsole"); +user_pref("devtools.toolsidebar-height.inspector", 350); +user_pref("devtools.toolsidebar-width.inspector", 700); +user_pref("devtools.toolsidebar-width.inspector.splitsidebar", 350); +user_pref("distribution.iniFile.exists.appversion", "78.0.1"); +user_pref("distribution.iniFile.exists.value", false); +user_pref("doh-rollout.balrog-migration-done", true); +user_pref("dom.push.testing.ignorePermission", true); +user_pref("dom.push.userAgentID", "6a0f4d925c28453fb75e25e1adcdf629"); +user_pref("extensions.activeThemeID", "default-theme@mozilla.org"); +user_pref("extensions.blocklist.pingCountVersion", -1); +user_pref("extensions.databaseSchema", 32); +user_pref("extensions.getAddons.cache.lastUpdate", 1594124375); +user_pref("extensions.getAddons.databaseSchema", 6); +user_pref("extensions.incognito.migrated", true); +user_pref("extensions.lastAppBuildId", "20200630195452"); +user_pref("extensions.lastAppVersion", "78.0.1"); +user_pref("extensions.lastPlatformVersion", "78.0.1"); +user_pref("extensions.pendingOperations", false); +user_pref("extensions.systemAddonSet", "{\"schema\":1,\"addons\":{}}"); +user_pref("extensions.webcompat.perform_injections", true); +user_pref("extensions.webcompat.perform_ua_overrides", true); +user_pref("extensions.webextensions.ExtensionStorageIDB.migrated.doh-rollout@mozilla.org", true); +user_pref("extensions.webextensions.ExtensionStorageIDB.migrated.screenshots@mozilla.org", true); +user_pref("extensions.webextensions.uuids", "{\"doh-rollout@mozilla.org\":\"f115427a-7102-f84f-8618-e29680a44787\",\"formautofill@mozilla.org\":\"3e4dd8c1-a9cb-554a-8451-3b8956527da2\",\"screenshots@mozilla.org\":\"66a5dbdd-ea49-5345-bed1-657b0ddbd6db\",\"webcompat-reporter@mozilla.org\":\"96d361b2-a359-ff45-8439-22a0fa91efff\",\"webcompat@mozilla.org\":\"e77f8b05-5155-cc4e-a672-4bcb47b8cd24\",\"default-theme@mozilla.org\":\"dafea455-5ea5-ef48-9358-eff17d987c47\",\"google@search.mozilla.org\":\"4e7a2822-0b3e-3541-85a6-89385d6ebe87\",\"amazondotcom@search.mozilla.org\":\"7d056c34-93b3-6a4a-9804-ad14f6804797\",\"bing@search.mozilla.org\":\"1a231ea5-c07b-7c48-a20c-9015a9ebb966\",\"ddg@search.mozilla.org\":\"dea6cf6e-0583-fd47-b64c-d2a2fa375940\",\"ebay@search.mozilla.org\":\"47a7d7fc-1402-8547-95a5-344534d3cc11\",\"twitter@search.mozilla.org\":\"b96fdf35-1726-9445-98b1-7fae53fa4851\",\"wikipedia@search.mozilla.org\":\"535d89c9-4972-884f-b042-c30835ca8dd7\"}"); +user_pref("font.internaluseonly.changed", false); +user_pref("ignorePermission", true); +user_pref("media.gmp.storage.version.observed", 1); +user_pref("notification.prompt.testing", true); +user_pref("notification.prompt.testing.allow", true); +user_pref("pdfjs.enabledCache.state", false); +user_pref("pdfjs.migrationVersion", 2); +user_pref("places.history.expiration.transient_current_max_pages", 112348); +user_pref("privacy.sanitize.pending", "[]"); +user_pref("security.sandbox.content.tempDirSuffix", "ea145204-9bb4-be4c-b91c-f2fea563fbbd"); +user_pref("security.sandbox.plugin.tempDirSuffix", "7b9233da-90e4-1445-8523-528f88b61d9c"); +user_pref("services.settings.clock_skew_seconds", -7); +user_pref("services.settings.last_update_seconds", 1594124300); +user_pref("signon.importedFromSqlite", true); +user_pref("toolkit.startup.last_success", 1594124800); +user_pref("toolkit.telemetry.cachedClientID", "bb098d55-d277-d945-9ea5-a02be78e3849"); +user_pref("toolkit.telemetry.previousBuildID", "20200630195452"); +user_pref("toolkit.telemetry.reportingpolicy.firstRun", false); +user_pref("trailhead.firstrun.didSeeAboutWelcome", true); diff --git a/testcafe/firefox/serviceworker.txt b/testcafe/firefox/serviceworker.txt new file mode 100644 index 0000000..23a8b58 --- /dev/null +++ b/testcafe/firefox/serviceworker.txt @@ -0,0 +1,111 @@ +8 + +http://localhost:5000/ +http://localhost:5000/sw.js +false +{1097743d-2157-4f49-bad9-32f9e29e6434} +0 +1594123666266241 +1594123666271465 +1594123666229378 +# + +http://localhost:12345/AKk1qaN5n!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/AKk1qaN5n!s!iso-8859-1/http://localhost:5000/sw.js +false +{c06a56e0-0d20-2148-82f8-ae12e411ef75} +0 +1594124148056423 +1594124148064185 +1594124147967200 +# + +http://localhost:12345/ozi0iin_T!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/ozi0iin_T!s!iso-8859-1/http://localhost:5000/sw.js +false +{5977e88e-689a-cd40-b1c6-fa0d94d2732f} +0 +1594124172217446 +1594124172225287 +1594124172126847 +# + +http://localhost:12345/2CyNTtFfs!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/2CyNTtFfs!s!iso-8859-1/http://localhost:5000/sw.js +false +{c5595ca5-6415-1c46-891f-823b456ab696} +0 +1594124243775235 +1594124243778084 +1594124243725459 +# + +http://localhost:12345/mamcTK9RP!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/mamcTK9RP!s!iso-8859-1/http://localhost:5000/sw.js +false +{9161ae38-3592-d141-9b01-b6227bc47513} +0 +1594124348762126 +1594124348762826 +1594124348732196 +# + +http://localhost:12345/0ZYPH1GHV!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/0ZYPH1GHV!s!iso-8859-1/http://localhost:5000/sw.js +false +{197019de-b475-1645-b149-de3d5a2c1494} +0 +1594124488537458 +1594124488539914 +1594124488502357 +# + +http://localhost:12345/oDWOBuiRT!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/oDWOBuiRT!s!iso-8859-1/http://localhost:5000/sw.js +false +{b0a91cfd-1b87-af42-8e02-c7336c35d69c} +0 +1594124587149727 +1594124587160709 +1594124587114408 +# + +http://localhost:12345/D44iN3JGm!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/D44iN3JGm!s!iso-8859-1/http://localhost:5000/sw.js +false +{0ea8aabd-1801-654b-b12f-53079bb212d0} +0 +1594124607853125 +1594124607865922 +1594124607794469 +# + +http://localhost:12345/kBarO5QZC!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/kBarO5QZC!s!iso-8859-1/http://localhost:5000/sw.js +false +{d39483ed-6620-ed45-aff2-067829647bea} +0 +1594124716042991 +1594124716059483 +1594124715987491 +# + +http://localhost:12345/691TvRQwQ!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/691TvRQwQ!s!iso-8859-1/http://localhost:5000/sw.js +false +{018515e8-707d-4e43-850b-6034879e9df0} +0 +1594124805285915 +1594124805294450 +1594124805195300 +# + +http://localhost:12345/1GDEWEjSN!s!iso-8859-1/http://localhost:5000/ +http://localhost:12345/1GDEWEjSN!s!iso-8859-1/http://localhost:5000/sw.js +false +{9dd4dc0a-c47c-e24d-becc-2542d08a7430} +0 +1594124838984184 +1594124838991677 +1594124838954254 +# diff --git a/testcafe/firefox/sessionCheckpoints.json b/testcafe/firefox/sessionCheckpoints.json new file mode 100644 index 0000000..928de6a --- /dev/null +++ b/testcafe/firefox/sessionCheckpoints.json @@ -0,0 +1 @@ +{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true} \ No newline at end of file diff --git a/testcafe/firefox/sessionstore.jsonlz4 b/testcafe/firefox/sessionstore.jsonlz4 new file mode 100644 index 0000000..ca00bea Binary files /dev/null and b/testcafe/firefox/sessionstore.jsonlz4 differ diff --git a/testcafe/firefox/storage/default/http+++localhost+12345/.metadata-v2 b/testcafe/firefox/storage/default/http+++localhost+12345/.metadata-v2 new file mode 100644 index 0000000..3abca92 Binary files /dev/null and b/testcafe/firefox/storage/default/http+++localhost+12345/.metadata-v2 differ diff --git a/testcafe/firefox/storage/default/http+++localhost+12345/cache/.padding b/testcafe/firefox/storage/default/http+++localhost+12345/cache/.padding new file mode 100644 index 0000000..1b1cb4d Binary files /dev/null and b/testcafe/firefox/storage/default/http+++localhost+12345/cache/.padding differ diff --git a/testcafe/firefox/storage/default/http+++localhost+12345/cache/caches.sqlite b/testcafe/firefox/storage/default/http+++localhost+12345/cache/caches.sqlite new file mode 100644 index 0000000..f4044f1 Binary files /dev/null and b/testcafe/firefox/storage/default/http+++localhost+12345/cache/caches.sqlite differ diff --git a/testcafe/firefox/storage/default/http+++localhost+12345/cache/morgue/211/{e6b6cbf7-e374-624d-ba52-4c59c4f77bd3}.final b/testcafe/firefox/storage/default/http+++localhost+12345/cache/morgue/211/{e6b6cbf7-e374-624d-ba52-4c59c4f77bd3}.final new file mode 100644 index 0000000..6be8e74 Binary files /dev/null and b/testcafe/firefox/storage/default/http+++localhost+12345/cache/morgue/211/{e6b6cbf7-e374-624d-ba52-4c59c4f77bd3}.final differ diff --git a/testcafe/firefox/xulstore.json b/testcafe/firefox/xulstore.json new file mode 100644 index 0000000..91cf717 --- /dev/null +++ b/testcafe/firefox/xulstore.json @@ -0,0 +1 @@ +{"chrome://browser/content/browser.xhtml":{"main-window":{"screenX":"4","screenY":"23","width":"1280","height":"1062","sizemode":"normal"},"sidebar-box":{"sidebarcommand":"","width":""},"sidebar-title":{"value":""}}} \ No newline at end of file diff --git a/testcafe/test.js b/testcafe/test.js new file mode 100644 index 0000000..524b0b4 --- /dev/null +++ b/testcafe/test.js @@ -0,0 +1,22 @@ +import { Selector } from 'testcafe'; + +fixture `End-to-end test` + .page `http://localhost:5000`; + +const subscription = Selector('input#subscription'); +const options = { + timeout: 30000 +}; + +test('End-to-end test', async t => { + await t + .expect(Selector('dd#service-worker').innerText).eql('y', '', options) + .expect(Selector('dd#show-notification').innerText).eql('y', '', options) + .expect(Selector('dd#notification-permission').innerText).eql('y', '', options) + .expect(Selector('dd#push-manager').innerText).eql('y', '', options) + .expect(Selector('dd#service-worker-ready').innerText).eql('y', '', options) + .expect(Selector('dd#subscription-ready').innerText).eql('y', '', options) + .expect(subscription.value).notEql('', 'subscription input is empty', options) + .click('#send') + .expect(Selector('li').exists).ok('', options); +});