blob: 97e533d4988106a3369c293d400e2465d4da88cb [file]
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you 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.openqa.selenium;
import static java.time.Instant.ofEpochMilli;
import static java.time.ZoneId.systemDefault;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.openqa.selenium.support.ui.ExpectedConditions.visibilityOf;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.bidi.log.ConsoleLogEntry;
import org.openqa.selenium.bidi.log.JavascriptLogEntry;
import org.openqa.selenium.bidi.log.LogLevel;
import org.openqa.selenium.remote.DomMutation;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.Script;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.testing.JupiterTestBase;
import org.openqa.selenium.testing.NeedsFreshDriver;
class WebScriptTest extends JupiterTestBase {
private String page;
@Test
@NeedsFreshDriver
void canAddConsoleMessageHandler()
throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<ConsoleLogEntry> future = new CompletableFuture<>();
String id = ((RemoteWebDriver) driver).script().addConsoleMessageHandler(future::complete);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
driver.findElement(By.id("consoleLog")).click();
ConsoleLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
assertThat(logEntry.getText()).isEqualTo("Hello, world!");
assertThat(logEntry.getArgs()).hasSize(1);
assertThat(logEntry.getArgs().get(0).getType()).isEqualTo("string");
assertThat(logEntry.getType()).isEqualTo("console");
assertThat(logEntry.getLevel()).isEqualTo(LogLevel.INFO);
assertThat(logEntry.getMethod()).isEqualTo("log");
((RemoteWebDriver) driver).script().removeConsoleMessageHandler(id);
}
@Test
@NeedsFreshDriver
void canRemoveConsoleMessageHandler()
throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<ConsoleLogEntry> future1 = new CompletableFuture<>();
CompletableFuture<ConsoleLogEntry> future2 = new CompletableFuture<>();
// Adding two consumers
Consumer<ConsoleLogEntry> consumer1 = future1::complete;
Consumer<ConsoleLogEntry> consumer2 = future2::complete;
String id1 = ((RemoteWebDriver) driver).script().addConsoleMessageHandler(consumer1);
String id2 = ((RemoteWebDriver) driver).script().addConsoleMessageHandler(consumer2);
// Removing the second consumer, so it will no longer get the console message.
((RemoteWebDriver) driver).script().removeConsoleMessageHandler(id2);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
driver.findElement(By.id("consoleLog")).click();
ConsoleLogEntry logEntry = future1.get(5, TimeUnit.SECONDS);
assertThat(logEntry.getText()).isEqualTo("Hello, world!");
assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS))
.as("Should be able to read the console messages")
.isInstanceOf(TimeoutException.class);
((RemoteWebDriver) driver).script().removeConsoleMessageHandler(id1);
}
@Test
@NeedsFreshDriver
void canAddJsErrorHandler() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<JavascriptLogEntry> future = new CompletableFuture<>();
String id = ((RemoteWebDriver) driver).script().addJavaScriptErrorHandler(future::complete);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
driver.findElement(By.id("jsException")).click();
JavascriptLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
assertThat(logEntry.getText()).isEqualTo("Error: Not working");
assertThat(logEntry.getType()).isEqualTo("javascript");
assertThat(logEntry.getLevel()).isEqualTo(LogLevel.ERROR);
((RemoteWebDriver) driver).script().removeJavaScriptErrorHandler(id);
}
@Test
@NeedsFreshDriver
void canRemoveJsErrorHandler() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<JavascriptLogEntry> future1 = new CompletableFuture<>();
CompletableFuture<JavascriptLogEntry> future2 = new CompletableFuture<>();
// Adding two consumers
Consumer<JavascriptLogEntry> consumer1 = future1::complete;
Consumer<JavascriptLogEntry> consumer2 = future2::complete;
String id1 = ((RemoteWebDriver) driver).script().addJavaScriptErrorHandler(consumer1);
String id2 = ((RemoteWebDriver) driver).script().addJavaScriptErrorHandler(consumer2);
// Removing the second consumer, so it will no longer get the JS error.
((RemoteWebDriver) driver).script().removeJavaScriptErrorHandler(id2);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
driver.findElement(By.id("jsException")).click();
JavascriptLogEntry logEntry = future1.get(5, TimeUnit.SECONDS);
assertThat(logEntry.getText()).isEqualTo("Error: Not working");
assertThat(logEntry.getType()).isEqualTo("javascript");
assertThat(logEntry.getLevel()).isEqualTo(LogLevel.ERROR);
assertThatThrownBy(() -> future2.get(5, TimeUnit.SECONDS))
.as("Should be able to read the JS errors")
.isInstanceOf(TimeoutException.class);
((RemoteWebDriver) driver).script().removeConsoleMessageHandler(id1);
}
@Test
@NeedsFreshDriver
void canAddMultipleHandlers() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<JavascriptLogEntry> future1 = new CompletableFuture<>();
CompletableFuture<JavascriptLogEntry> future2 = new CompletableFuture<>();
// Adding two consumers
Consumer<JavascriptLogEntry> consumer1 = future1::complete;
Consumer<JavascriptLogEntry> consumer2 = future2::complete;
String id1 = ((RemoteWebDriver) driver).script().addJavaScriptErrorHandler(consumer1);
String id2 = ((RemoteWebDriver) driver).script().addJavaScriptErrorHandler(consumer2);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
driver.findElement(By.id("jsException")).click();
JavascriptLogEntry logEntry1 = future1.get(5, TimeUnit.SECONDS);
assertThat(logEntry1.getText()).isEqualTo("Error: Not working");
assertThat(logEntry1.getType()).isEqualTo("javascript");
assertThat(logEntry1.getLevel()).isEqualTo(LogLevel.ERROR);
JavascriptLogEntry logEntry2 = future2.get(5, TimeUnit.SECONDS);
assertThat(logEntry2.getText()).isEqualTo("Error: Not working");
assertThat(logEntry2.getType()).isEqualTo("javascript");
assertThat(logEntry2.getLevel()).isEqualTo(LogLevel.ERROR);
}
@Test
@NeedsFreshDriver
void canAddDomMutationHandler() {
List<String> mutations = new CopyOnWriteArrayList<>();
Script script = ((RemoteWebDriver) driver).script();
script.addDomMutationHandler(mutationHandler(mutations));
driver.get(pages.dynamicPage);
triggerDomMutation();
assertThat(mutations).isNotEmpty();
assertThat(lastOf(mutations)).isEqualTo("style: 'display:none;' -> ''");
}
@Test
@NeedsFreshDriver
void canRemoveDomMutationHandler() {
List<String> mutations = new CopyOnWriteArrayList<>();
Script script = ((RemoteWebDriver) driver).script();
String id = script.addDomMutationHandler(mutationHandler(mutations));
driver.get(pages.dynamicPage);
triggerDomMutation();
assertThat(mutations).isNotEmpty();
script.removeDomMutationHandler(id);
mutations.clear();
driver.get(pages.dynamicPage);
triggerDomMutation();
assertThat(mutations).isEmpty();
}
private void triggerDomMutation() {
WebElement reveal = driver.findElement(By.id("reveal"));
reveal.click();
WebElement revealed = driver.findElement(By.id("revealed"));
new WebDriverWait(driver, Duration.ofSeconds(10)).until(visibilityOf(revealed));
}
private static Consumer<DomMutation> mutationHandler(List<String> mutations) {
return mutation -> {
mutations.add(
String.format(
"%s: '%s' -> '%s'",
mutation.getAttributeName(), mutation.getOldValue(), mutation.getCurrentValue()));
};
}
@Test
@NeedsFreshDriver
void canPinScript() throws ExecutionException, InterruptedException, TimeoutException {
CompletableFuture<ConsoleLogEntry> future = new CompletableFuture<>();
((RemoteWebDriver) driver).script().pin("() => { console.log('Hello!'); }");
String id = ((RemoteWebDriver) driver).script().addConsoleMessageHandler(future::complete);
page = appServer.whereIs("/bidi/logEntryAdded.html");
driver.get(page);
ConsoleLogEntry logEntry = future.get(5, TimeUnit.SECONDS);
assertThat(logEntry.getText()).isEqualTo("Hello!");
((RemoteWebDriver) driver).script().removeConsoleMessageHandler(id);
}
@Test
@NeedsFreshDriver
void canUnpinScript() throws InterruptedException {
List<String> logs = new CopyOnWriteArrayList<>();
CountDownLatch latch = new CountDownLatch(1);
Script script = ((RemoteWebDriver) driver).script();
String pinnedScript = script.pin("() => { console.log('Hello!'); }");
DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("HH:mm:ss:SSS").withZone(systemDefault());
String id =
script.addConsoleMessageHandler(
log -> {
String time = formatter.format(ofEpochMilli(log.getTimestamp()));
String message = String.format("%s %s", log.getText(), time);
logs.add(message);
latch.countDown();
});
try {
page = appServer.whereIs("/bidi/logEntryAdded.html");
assertThat(logs).hasSize(0);
driver.get(page);
assertThat(latch.await(10, SECONDS)).isTrue();
assertThat(logs).as("Chrome logs once, FireFox logs twice").isNotEmpty();
assertThat(logs.get(0)).startsWith("Hello!");
script.unpin(pinnedScript);
logs.clear();
driver.get(page);
assertThat(logs).as("Script has been unpinned, no logs anymore.").isEmpty();
} finally {
script.removeConsoleMessageHandler(id);
}
}
private static <T> T lastOf(List<T> list) {
return list.get(list.size() - 1);
}
}