blob: 219fe1819cd6701905d521d05a6c2973281ef2a0 [file] [log] [blame] [edit]
// <copyright file="ExecutingAsyncJavascriptTest.cs" company="Selenium Committers">
// 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.
// </copyright>
using NUnit.Framework;
using System;
using System.Collections.ObjectModel;
namespace OpenQA.Selenium;
[TestFixture]
public class ExecutingAsyncJavascriptTest : DriverTestFixture
{
private IJavaScriptExecutor executor;
private TimeSpan originalTimeout = TimeSpan.MinValue;
[SetUp]
public void SetUpEnvironment()
{
if (driver is IJavaScriptExecutor)
{
executor = (IJavaScriptExecutor)driver;
}
try
{
originalTimeout = driver.Manage().Timeouts().AsynchronousJavaScript;
}
catch (NotImplementedException)
{
// For driver implementations that do not support getting timeouts,
// just set a default 30-second timeout.
originalTimeout = TimeSpan.FromSeconds(30);
}
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(1);
}
[TearDown]
public void TearDownEnvironment()
{
driver.Manage().Timeouts().AsynchronousJavaScript = originalTimeout;
}
[Test]
public void ShouldNotTimeoutIfCallbackInvokedImmediately()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1](123);");
Assert.That(result, Is.InstanceOf<long>());
Assert.That((long)result, Is.EqualTo(123));
}
[Test]
public void ShouldBeAbleToReturnJavascriptPrimitivesFromAsyncScripts_NeitherNullNorUndefined()
{
driver.Url = ajaxyPage;
Assert.That((long)executor.ExecuteAsyncScript("arguments[arguments.length - 1](123);"), Is.EqualTo(123));
driver.Url = ajaxyPage;
Assert.That(executor.ExecuteAsyncScript("arguments[arguments.length - 1]('abc');").ToString(), Is.EqualTo("abc"));
driver.Url = ajaxyPage;
Assert.That((bool)executor.ExecuteAsyncScript("arguments[arguments.length - 1](false);"), Is.False);
driver.Url = ajaxyPage;
Assert.That((bool)executor.ExecuteAsyncScript("arguments[arguments.length - 1](true);"), Is.True);
}
[Test]
public void ShouldBeAbleToReturnJavascriptPrimitivesFromAsyncScripts_NullAndUndefined()
{
driver.Url = ajaxyPage;
Assert.That(executor.ExecuteAsyncScript("arguments[arguments.length - 1](null);"), Is.Null);
Assert.That(executor.ExecuteAsyncScript("arguments[arguments.length - 1]();"), Is.Null);
}
[Test]
public void ShouldBeAbleToReturnAnArrayLiteralFromAnAsyncScript()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1]([]);");
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<ReadOnlyCollection<object>>());
Assert.That((ReadOnlyCollection<object>)result, Has.Count.EqualTo(0));
}
[Test]
public void ShouldBeAbleToReturnAnArrayObjectFromAnAsyncScript()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1](new Array());");
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<ReadOnlyCollection<object>>());
Assert.That((ReadOnlyCollection<object>)result, Has.Count.EqualTo(0));
}
[Test]
public void ShouldBeAbleToReturnArraysOfPrimitivesFromAsyncScripts()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1]([null, 123, 'abc', true, false]);");
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<ReadOnlyCollection<object>>());
ReadOnlyCollection<object> resultList = result as ReadOnlyCollection<object>;
Assert.That(resultList, Has.Count.EqualTo(5));
Assert.That(resultList[0], Is.Null);
Assert.That((long)resultList[1], Is.EqualTo(123));
Assert.That(resultList[2].ToString(), Is.EqualTo("abc"));
Assert.That((bool)resultList[3], Is.True);
Assert.That((bool)resultList[4], Is.False);
}
[Test]
public void ShouldBeAbleToReturnWebElementsFromAsyncScripts()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1](document.body);");
Assert.That(result, Is.InstanceOf<IWebElement>());
Assert.That(((IWebElement)result).TagName.ToLower(), Is.EqualTo("body"));
}
[Test]
public void ShouldBeAbleToReturnArraysOfWebElementsFromAsyncScripts()
{
driver.Url = ajaxyPage;
object result = executor.ExecuteAsyncScript("arguments[arguments.length - 1]([document.body, document.body]);");
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.InstanceOf<ReadOnlyCollection<IWebElement>>());
ReadOnlyCollection<IWebElement> resultsList = (ReadOnlyCollection<IWebElement>)result;
Assert.That(resultsList, Has.Count.EqualTo(2));
Assert.That(resultsList[0], Is.InstanceOf<IWebElement>());
Assert.That(resultsList[1], Is.InstanceOf<IWebElement>());
Assert.That(((IWebElement)resultsList[0]).TagName.ToLower(), Is.EqualTo("body"));
Assert.That(((IWebElement)resultsList[0]), Is.EqualTo((IWebElement)resultsList[1]));
}
[Test]
public void ShouldTimeoutIfScriptDoesNotInvokeCallback()
{
driver.Url = ajaxyPage;
Assert.That(() => executor.ExecuteAsyncScript("return 1 + 2;"), Throws.InstanceOf<WebDriverTimeoutException>());
}
[Test]
public void ShouldTimeoutIfScriptDoesNotInvokeCallbackWithAZeroTimeout()
{
driver.Url = ajaxyPage;
Assert.That(() => executor.ExecuteAsyncScript("window.setTimeout(function() {}, 0);"), Throws.InstanceOf<WebDriverTimeoutException>());
}
[Test]
public void ShouldNotTimeoutIfScriptCallsbackInsideAZeroTimeout()
{
driver.Url = ajaxyPage;
executor.ExecuteAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"window.setTimeout(function() { callback(123); }, 0)");
}
[Test]
public void ShouldTimeoutIfScriptDoesNotInvokeCallbackWithLongTimeout()
{
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromMilliseconds(500);
driver.Url = ajaxyPage;
Assert.That(() => executor.ExecuteAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"window.setTimeout(callback, 1500);"), Throws.InstanceOf<WebDriverTimeoutException>());
}
[Test]
public void ShouldDetectPageLoadsWhileWaitingOnAnAsyncScriptAndReturnAnError()
{
driver.Url = ajaxyPage;
Assert.That(() => executor.ExecuteAsyncScript("window.location = '" + dynamicPage + "';"), Throws.InstanceOf<WebDriverException>());
}
[Test]
public void ShouldCatchErrorsWhenExecutingInitialScript()
{
driver.Url = ajaxyPage;
Assert.That(() => executor.ExecuteAsyncScript("throw Error('you should catch this!');"), Throws.InstanceOf<WebDriverException>());
}
[Test]
public void ShouldNotTimeoutWithMultipleCallsTheFirstOneBeingSynchronous()
{
driver.Url = ajaxyPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromMilliseconds(1000);
Assert.That((bool)executor.ExecuteAsyncScript("arguments[arguments.length - 1](true);"), Is.True);
Assert.That((bool)executor.ExecuteAsyncScript("var cb = arguments[arguments.length - 1]; window.setTimeout(function(){cb(true);}, 9);"), Is.True);
}
[Test]
[IgnoreBrowser(Browser.Chrome, ".NET language bindings do not properly parse JavaScript stack trace")]
[IgnoreBrowser(Browser.Edge, ".NET language bindings do not properly parse JavaScript stack trace")]
[IgnoreBrowser(Browser.Firefox, ".NET language bindings do not properly parse JavaScript stack trace")]
[IgnoreBrowser(Browser.IE, ".NET language bindings do not properly parse JavaScript stack trace")]
[IgnoreBrowser(Browser.Safari, ".NET language bindings do not properly parse JavaScript stack trace")]
public void ShouldCatchErrorsWithMessageAndStacktraceWhenExecutingInitialScript()
{
driver.Url = ajaxyPage;
string js = "function functionB() { throw Error('errormessage'); };"
+ "function functionA() { functionB(); };"
+ "functionA();";
Assert.That(
() => executor.ExecuteAsyncScript(js),
Throws.InstanceOf<WebDriverException>()
.With.Message.Contains("errormessage")
.And.Property(nameof(WebDriverException.StackTrace)).Contains("functionB"));
}
[Test]
public void ShouldBeAbleToExecuteAsynchronousScripts()
{
// Reset the timeout to the 30-second default instead of zero.
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(30);
driver.Url = ajaxyPage;
IWebElement typer = driver.FindElement(By.Name("typer"));
typer.SendKeys("bob");
Assert.That(typer.GetAttribute("value"), Is.EqualTo("bob"));
driver.FindElement(By.Id("red")).Click();
driver.FindElement(By.Name("submit")).Click();
Assert.That(GetNumberOfDivElements(), Is.EqualTo(1), "There should only be 1 DIV at this point, which is used for the butter message");
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(10);
string text = (string)executor.ExecuteAsyncScript(
"var callback = arguments[arguments.length - 1];"
+ "window.registerListener(arguments[arguments.length - 1]);");
Assert.That(text, Is.EqualTo("bob"));
Assert.That(typer.GetAttribute("value"), Is.Empty);
Assert.That(GetNumberOfDivElements(), Is.EqualTo(2), "There should be 1 DIV (for the butter message) + 1 DIV (for the new label)");
}
[Test]
public void ShouldBeAbleToPassMultipleArgumentsToAsyncScripts()
{
driver.Url = ajaxyPage;
long result = (long)executor.ExecuteAsyncScript("arguments[arguments.length - 1](arguments[0] + arguments[1]);", 1, 2);
Assert.That(result, Is.EqualTo(3));
}
[Test]
public 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();";
driver.Url = ajaxyPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(3);
string response = (string)executor.ExecuteAsyncScript(script, sleepingPage + "?time=2");
Assert.That(response.Trim(), Is.EqualTo("<html><head><title>Done</title></head><body>Slept for 2s</body></html>"));
}
[Test]
public void ThrowsIfScriptTriggersAlert()
{
driver.Url = simpleTestPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(5);
((IJavaScriptExecutor)driver).ExecuteAsyncScript(
"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert('Look! An alert!'); }, 50);");
Assert.That(() => driver.Title, Throws.InstanceOf<UnhandledAlertException>());
string title = driver.Title;
}
[Test]
public void ThrowsIfAlertHappensDuringScript()
{
driver.Url = slowLoadingAlertPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(5);
((IJavaScriptExecutor)driver).ExecuteAsyncScript("setTimeout(arguments[0], 1000);");
Assert.That(() => driver.Title, Throws.InstanceOf<UnhandledAlertException>());
// Shouldn't throw
string title = driver.Title;
}
[Test]
public void ThrowsIfScriptTriggersAlertWhichTimesOut()
{
driver.Url = simpleTestPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(5);
((IJavaScriptExecutor)driver)
.ExecuteAsyncScript("setTimeout(function() { window.alert('Look! An alert!'); }, 50);");
Assert.That(() => driver.Title, Throws.InstanceOf<UnhandledAlertException>());
// Shouldn't throw
string title = driver.Title;
}
[Test]
public void ThrowsIfAlertHappensDuringScriptWhichTimesOut()
{
driver.Url = slowLoadingAlertPage;
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(5);
((IJavaScriptExecutor)driver).ExecuteAsyncScript("");
Assert.That(() => driver.Title, Throws.InstanceOf<UnhandledAlertException>());
// Shouldn't throw
string title = driver.Title;
}
[Test]
[IgnoreBrowser(Browser.Firefox, "Driver chooses not to return text from unhandled alert")]
public void IncludesAlertTextInUnhandledAlertException()
{
driver.Manage().Timeouts().AsynchronousJavaScript = TimeSpan.FromSeconds(5);
string alertText = "Look! An alert!";
((IJavaScriptExecutor)driver).ExecuteAsyncScript(
"setTimeout(arguments[0], 200) ; setTimeout(function() { window.alert('" + alertText
+ "'); }, 50);");
Assert.That(() => driver.Title, Throws.InstanceOf<UnhandledAlertException>().With.Property("AlertText").EqualTo(alertText));
}
private long GetNumberOfDivElements()
{
IJavaScriptExecutor jsExecutor = driver as IJavaScriptExecutor;
// Selenium does not support "findElements" yet, so we have to do this through a script.
return (long)jsExecutor.ExecuteScript("return document.getElementsByTagName('div').length;");
}
}