blob: d81496aec1865a764d3bb9cdecda99aac94bb8bc [file] [log] [blame]
// 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 com.google.common.base.Throwables.getRootCause;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.openqa.selenium.testing.drivers.Browser.CHROME;
import static org.openqa.selenium.testing.drivers.Browser.EDGE;
import static org.openqa.selenium.testing.drivers.Browser.FIREFOX;
import static org.openqa.selenium.testing.drivers.Browser.IE;
import static org.openqa.selenium.testing.drivers.Browser.SAFARI;
import java.time.Duration;
import java.util.Iterator;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.openqa.selenium.testing.Ignore;
import org.openqa.selenium.testing.JupiterTestBase;
import org.openqa.selenium.testing.NotYetImplemented;
class ExecutingAsyncJavascriptTest extends JupiterTestBase {
private JavascriptExecutor executor;
@BeforeEach
public void setUp() {
assumeTrue(driver instanceof JavascriptExecutor);
executor = (JavascriptExecutor) driver;
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
}
@Test
@NotYetImplemented(value = CHROME, reason = "Default to 5s")
@NotYetImplemented(value = EDGE, reason = "Default to 5s")
@NotYetImplemented(value = FIREFOX, reason = "Default to 5s")
@NotYetImplemented(value = SAFARI, reason = "Default to 5s")
public void shouldSetAndGetScriptTimeout() {
Duration timeout = driver.manage().timeouts().getScriptTimeout();
assertThat(timeout).hasMillis(30000);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(3000));
Duration timeout2 = driver.manage().timeouts().getScriptTimeout();
assertThat(timeout2).hasMillis(3000);
}
@Test
void shouldNotTimeoutIfCallbackInvokedImmediately() {
driver.get(pages.ajaxyPage);
Object result = executor.executeAsyncScript("arguments[arguments.length - 1](123);");
assertThat(result).isInstanceOf(Number.class);
assertThat(((Number) result).intValue()).isEqualTo(123);
}
@Test
void shouldBeAbleToReturnJavascriptPrimitivesFromAsyncScripts_NeitherNullNorUndefined() {
driver.get(pages.ajaxyPage);
assertThat(
((Number) executor.executeAsyncScript("arguments[arguments.length - 1](123);"))
.longValue())
.isEqualTo(123);
assertThat(executor.executeAsyncScript("arguments[arguments.length - 1]('abc');"))
.isEqualTo("abc");
assertThat((Boolean) executor.executeAsyncScript("arguments[arguments.length - 1](false);"))
.isFalse();
assertThat((Boolean) executor.executeAsyncScript("arguments[arguments.length - 1](true);"))
.isTrue();
}
@Test
void shouldBeAbleToReturnJavascriptPrimitivesFromAsyncScripts_NullAndUndefined() {
driver.get(pages.ajaxyPage);
assertThat(executor.executeAsyncScript("arguments[arguments.length - 1](null)")).isNull();
assertThat(executor.executeAsyncScript("arguments[arguments.length - 1]()")).isNull();
}
@Test
void shouldBeAbleToReturnAnArrayLiteralFromAnAsyncScript() {
driver.get(pages.ajaxyPage);
Object result = executor.executeAsyncScript("arguments[arguments.length - 1]([]);");
assertThat(result).isNotNull().isInstanceOf(List.class);
assertThat(((List<?>) result)).isEmpty();
}
@Test
void shouldBeAbleToReturnAnArrayObjectFromAnAsyncScript() {
driver.get(pages.ajaxyPage);
Object result = executor.executeAsyncScript("arguments[arguments.length - 1](new Array());");
assertThat(result).isNotNull().isInstanceOf(List.class);
assertThat(((List<?>) result)).isEmpty();
}
@Test
void shouldBeAbleToReturnArraysOfPrimitivesFromAsyncScripts() {
driver.get(pages.ajaxyPage);
Object result =
executor.executeAsyncScript(
"arguments[arguments.length - 1]([null, 123, 'abc', true, false]);");
assertThat(result).isNotNull();
assertThat(result).isInstanceOf(List.class);
Iterator<?> results = ((List<?>) result).iterator();
assertThat(results.next()).isNull();
assertThat(((Number) results.next()).longValue()).isEqualTo(123);
assertThat(results.next()).isEqualTo("abc");
assertThat((Boolean) results.next()).isTrue();
assertThat((Boolean) results.next()).isFalse();
assertThat(results.hasNext()).isFalse();
}
@Test
void shouldBeAbleToReturnWebElementsFromAsyncScripts() {
driver.get(pages.ajaxyPage);
Object result = executor.executeAsyncScript("arguments[arguments.length - 1](document.body);");
assertThat(result).isInstanceOf(WebElement.class);
assertThat(((WebElement) result).getTagName()).isEqualToIgnoringCase("body");
}
@Test
@Ignore(value = CHROME, reason = "https://bugs.chromium.org/p/chromedriver/issues/detail?id=4525")
void shouldBeAbleToReturnArraysOfWebElementsFromAsyncScripts() {
driver.get(pages.ajaxyPage);
Object result =
executor.executeAsyncScript(
"arguments[arguments.length - 1]([document.body, document.body]);");
assertThat(result).isNotNull().isInstanceOf(List.class);
List<?> list = (List<?>) result;
assertThat(list).hasSize(2);
assertThat(list.get(0)).isInstanceOf(WebElement.class);
assertThat(list.get(1)).isInstanceOf(WebElement.class);
assertThat(((WebElement) list.get(0)).getTagName()).isEqualToIgnoringCase("body");
assertThat(list.get(1)).isEqualTo(list.get(0));
}
@Test
@NotYetImplemented(SAFARI)
public void shouldTimeoutIfScriptDoesNotInvokeCallback() {
driver.get(pages.ajaxyPage);
// Script is expected to be async and explicitly callback, so this should timeout.
assertThatExceptionOfType(ScriptTimeoutException.class)
.isThrownBy(() -> executor.executeAsyncScript("return 1 + 2;"));
}
@Test
@NotYetImplemented(SAFARI)
public void shouldTimeoutIfScriptDoesNotInvokeCallbackWithAZeroTimeout() {
driver.get(pages.ajaxyPage);
assertThatExceptionOfType(ScriptTimeoutException.class)
.isThrownBy(() -> executor.executeAsyncScript("window.setTimeout(function() {}, 0);"));
}
@Test
@Ignore(FIREFOX)
public void shouldNotTimeoutIfScriptCallsbackInsideAZeroTimeout() {
driver.get(pages.ajaxyPage);
executor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];"
+ "window.setTimeout(function() { callback(123); }, 0)");
}
@Test
@NotYetImplemented(SAFARI)
public void shouldTimeoutIfScriptDoesNotInvokeCallbackWithLongTimeout() {
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(500));
driver.get(pages.ajaxyPage);
assertThatExceptionOfType(ScriptTimeoutException.class)
.isThrownBy(
() ->
executor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];"
+ "window.setTimeout(callback, 1500);"));
}
@Test
@Ignore(IE)
public void shouldDetectPageLoadsWhileWaitingOnAnAsyncScriptAndReturnAnError() {
driver.get(pages.ajaxyPage);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(100));
assertThatExceptionOfType(WebDriverException.class)
.isThrownBy(
() -> executor.executeAsyncScript("window.location = '" + pages.dynamicPage + "';"));
}
@Test
void shouldCatchErrorsWhenExecutingInitialScript() {
driver.get(pages.ajaxyPage);
assertThatExceptionOfType(WebDriverException.class)
.isThrownBy(() -> executor.executeAsyncScript("throw Error('you should catch this!');"));
}
@Test
void shouldNotTimeoutWithMultipleCallsTheFirstOneBeingSynchronous() {
driver.get(pages.ajaxyPage);
assertThat((Boolean) executor.executeAsyncScript("arguments[arguments.length - 1](true);"))
.isTrue();
assertThat(
(Boolean)
executor.executeAsyncScript(
"var cb = arguments[arguments.length - 1];"
+ " window.setTimeout(function(){cb(true);}, 9);"))
.isTrue();
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@NotYetImplemented(SAFARI)
@Ignore(FIREFOX)
public void shouldCatchErrorsWithMessageAndStacktraceWhenExecutingInitialScript() {
driver.get(pages.ajaxyPage);
String js =
"function functionB() { throw Error('errormessage'); };"
+ "function functionA() { functionB(); };"
+ "functionA();";
assertThatExceptionOfType(WebDriverException.class)
.isThrownBy(() -> executor.executeAsyncScript(js))
.withMessageContaining("errormessage")
.satisfies(
t -> {
Throwable rootCause = getRootCause(t);
assertThat(rootCause).hasMessageContaining("errormessage");
assertThat(List.of(rootCause.getStackTrace()))
.extracting(StackTraceElement::getMethodName)
.contains("functionB");
});
}
@Test
void shouldBeAbleToExecuteAsynchronousScripts() {
driver.get(pages.ajaxyPage);
WebElement typer = driver.findElement(By.name("typer"));
typer.sendKeys("bob");
assertThat(typer.getAttribute("value")).isEqualTo("bob");
driver.findElement(By.id("red")).click();
driver.findElement(By.name("submit")).click();
assertThat(getNumDivElements())
.describedAs(
"There should only be 1 DIV at this point, which is used for the butter message")
.isEqualTo(1);
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(15));
String text =
(String)
executor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];"
+ "window.registerListener(arguments[arguments.length - 1]);");
assertThat(text).isEqualTo("bob");
assertThat(typer.getAttribute("value")).isEmpty();
assertThat(getNumDivElements())
.describedAs("There should be 1 DIV (for the butter message) + 1 DIV (for the new label)")
.isEqualTo(2);
}
@Test
void shouldBeAbleToPassMultipleArgumentsToAsyncScripts() {
driver.get(pages.ajaxyPage);
Number result =
(Number)
executor.executeAsyncScript(
"arguments[arguments.length - 1](arguments[0] + arguments[1]);", 1, 2);
assertThat(result.intValue()).isEqualTo(3);
}
@Test
void shouldBeAbleToMakeXMLHttpRequestsAndWaitForTheResponse() {
String script =
"var url = arguments[0];"
+ "var callback = arguments[arguments.length - 1];"
+
// Adapted from http://www.quirksmode.org/js/xmlhttp.html
"var XMLHttpFactories = ["
+ " function () {return new XMLHttpRequest()},"
+ " function () {return new ActiveXObject('Msxml2.XMLHTTP')},"
+ " function () {return new ActiveXObject('Msxml3.XMLHTTP')},"
+ " function () {return new ActiveXObject('Microsoft.XMLHTTP')}"
+ "];"
+ "var xhr = false;"
+ "while (!xhr && XMLHttpFactories.length) {"
+ " try {"
+ " xhr = XMLHttpFactories.shift().call();"
+ " } catch (e) {}"
+ "}"
+ "if (!xhr) throw Error('unable to create XHR object');"
+ "xhr.open('GET', url, true);"
+ "xhr.onreadystatechange = function() {"
+ " if (xhr.readyState == 4) callback(xhr.responseText);"
+ "};"
+ "xhr.send('');"; // empty string to stop firefox 3 from choking
driver.get(pages.ajaxyPage);
driver.manage().timeouts().scriptTimeout(Duration.ofSeconds(3));
String response = (String) executor.executeAsyncScript(script, pages.sleepingPage + "?time=2");
assertThat(response.trim())
.isEqualTo("<html><head><title>Done</title></head><body>Slept for 2s</body></html>");
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@Ignore(FIREFOX)
@Ignore(value = SAFARI, reason = "Does not support alerts yet")
public void throwsIfScriptTriggersAlert() {
driver.get(pages.simpleTestPage);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
assertThatExceptionOfType(UnhandledAlertException.class)
.isThrownBy(
() ->
executor.executeAsyncScript(
"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert('Look! An"
+ " alert!'); }, 50);"));
// Shouldn't throw
driver.getTitle();
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@Ignore(FIREFOX)
@Ignore(value = SAFARI, reason = "Does not support alerts yet")
public void throwsIfAlertHappensDuringScript() {
driver.get(pages.slowLoadingAlertPage);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
assertThatExceptionOfType(UnhandledAlertException.class)
.isThrownBy(() -> executor.executeAsyncScript("setTimeout(arguments[0], 1000);"));
// Shouldn't throw
driver.getTitle();
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@Ignore(FIREFOX)
@Ignore(value = SAFARI, reason = "Does not support alerts yet")
public void throwsIfScriptTriggersAlertWhichTimesOut() {
driver.get(pages.simpleTestPage);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
assertThatExceptionOfType(UnhandledAlertException.class)
.isThrownBy(
() ->
executor.executeAsyncScript(
"setTimeout(function() { window.alert('Look! An alert!'); }, 50);"));
// Shouldn't throw
driver.getTitle();
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@Ignore(FIREFOX)
@Ignore(value = SAFARI, reason = "Does not support alerts yet")
public void throwsIfAlertHappensDuringScriptWhichTimesOut() {
driver.get(pages.slowLoadingAlertPage);
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
assertThatExceptionOfType(UnhandledAlertException.class)
.isThrownBy(() -> executor.executeAsyncScript(""));
// Shouldn't throw
driver.getTitle();
}
@Test
@Ignore(CHROME)
@Ignore(EDGE)
@Ignore(IE)
@Ignore(FIREFOX)
@Ignore(value = SAFARI, reason = "Does not support alerts yet")
public void includesAlertTextInUnhandledAlertException() {
driver.manage().timeouts().scriptTimeout(Duration.ofMillis(5000));
String alertText = "Look! An alert!";
assertThatExceptionOfType(UnhandledAlertException.class)
.isThrownBy(
() ->
executor.executeAsyncScript(
"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert('"
+ alertText
+ "'); }, 50);"))
.satisfies(t -> assertThat(t.getAlertText()).isEqualTo(alertText));
}
private long getNumDivElements() {
// Selenium does not support "findElements" yet, so we have to do this through a script.
return (Long)
((JavascriptExecutor) driver)
.executeScript("return document.getElementsByTagName('div').length;");
}
}