| // <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;"); |
| } |
| } |