// <copyright file="ExecutingJavascriptTest.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.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace OpenQA.Selenium
{
    [TestFixture]
    public class ExecutingJavascriptTest : DriverTestFixture
    {
        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAString()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = xhtmlTestPage;

            object result = ExecuteScript("return document.title;");

            Assert.That(result, Is.InstanceOf<string>());
            Assert.That(result, Is.EqualTo("XHTML Test Page"));
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnALong()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = xhtmlTestPage;

            object result = ExecuteScript("return document.title.length;");

            Assert.That(result, Is.InstanceOf<long>());
            Assert.That((long)result, Is.EqualTo((long)"XHTML Test Page".Length));
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAWebElement()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = xhtmlTestPage;

            object result = ExecuteScript("return document.getElementById('id1');");

            Assert.That(result, Is.Not.Null);
            Assert.That(result, Is.InstanceOf<IWebElement>());
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnABoolean()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = xhtmlTestPage;

            object result = ExecuteScript("return true;");

            Assert.That(result, Is.Not.Null);
            Assert.That(result, Is.InstanceOf<bool>());
            Assert.That((bool)result, Is.True);
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAStringArray()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            List<object> expectedResult = new List<object>();
            expectedResult.Add("zero");
            expectedResult.Add("one");
            expectedResult.Add("two");
            object result = ExecuteScript("return ['zero', 'one', 'two'];");
            Assert.That(result, Is.InstanceOf<ReadOnlyCollection<object>>());
            ReadOnlyCollection<object> list = (ReadOnlyCollection<object>)result;
            Assert.That(list, Is.EqualTo(expectedResult.AsReadOnly()));
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAnArray()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            List<object> expectedResult = new List<object>();
            expectedResult.Add("zero");
            List<object> subList = new List<object>();
            subList.Add(true);
            subList.Add(false);
            expectedResult.Add(subList.AsReadOnly());
            object result = ExecuteScript("return ['zero', [true, false]];");
            Assert.That(result, Is.InstanceOf<ReadOnlyCollection<object>>());
            ReadOnlyCollection<object> list = (ReadOnlyCollection<object>)result;
            Assert.That(result, Is.EqualTo(expectedResult.AsReadOnly()));
        }

        [Test]
        public void ShouldBeAbleToExecuteJavascriptAndReturnABasicObjectLiteral()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;

            object result = ExecuteScript("return {abc: '123', tired: false};");
            Assert.That(result, Is.InstanceOf<Dictionary<string, object>>());
            Dictionary<string, object> map = (Dictionary<string, object>)result;

            Dictionary<string, object> expected = new Dictionary<string, object>();
            expected.Add("abc", "123");
            expected.Add("tired", false);

            Assert.That(map, Has.Count.EqualTo(expected.Count), "Expected:<" + expected.Count + ">, but was:<" + map.Count + ">");
            foreach (string expectedKey in expected.Keys)
            {
                Assert.That(map, Does.ContainKey(expectedKey));
                Assert.That(map[expectedKey], Is.EqualTo(expected[expectedKey]));
            }
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAnObjectLiteral()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;

            Dictionary<string, object> expectedPerson = new Dictionary<string, object>();
            expectedPerson.Add("first", "John");
            expectedPerson.Add("last", "Doe");
            Dictionary<string, object> expectedResult = new Dictionary<string, object>();
            expectedResult.Add("foo", "bar");
            List<object> subList = new List<object>() { "a", "b", "c" };
            expectedResult.Add("baz", subList.AsReadOnly());
            expectedResult.Add("person", expectedPerson);

            object result = ExecuteScript(
                "return {foo:'bar', baz: ['a', 'b', 'c'], " +
                    "person: {first: 'John',last: 'Doe'}};");
            Assert.That(result, Is.InstanceOf<Dictionary<string, object>>());

            Dictionary<string, object> map = (Dictionary<string, object>)result;
            Assert.That(map, Has.Count.EqualTo(3));
            foreach (string expectedKey in expectedResult.Keys)
            {
                Assert.That(map, Does.ContainKey(expectedKey));
            }

            Assert.That(map["foo"], Is.EqualTo("bar"));
            Assert.That((ReadOnlyCollection<object>)map["baz"], Is.EqualTo((ReadOnlyCollection<object>)expectedResult["baz"]));

            Dictionary<string, object> person = (Dictionary<string, object>)map["person"];
            Assert.That(person, Has.Count.EqualTo(2));
            Assert.That(person["first"], Is.EqualTo("John"));
            Assert.That(person["last"], Is.EqualTo("Doe"));
        }

        [Test]
        public void ShouldBeAbleToExecuteSimpleJavascriptAndReturnAComplexObject()
        {
            driver.Url = javascriptPage;

            object result = ExecuteScript("return window.location;");

            Assert.That(result, Is.InstanceOf<Dictionary<string, object>>());
            Dictionary<string, object> map = (Dictionary<string, object>)result;
            Assert.That(map["protocol"], Is.EqualTo("http:"));
            Assert.That(map["href"], Is.EqualTo(javascriptPage));
        }

        [Test]
        public void PassingAndReturningALongShouldReturnAWholeNumber()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            long expectedResult = 1L;
            object result = ExecuteScript("return arguments[0];", expectedResult);
            Assert.That(result, Is.InstanceOf<int>().Or.InstanceOf<long>());
            Assert.That(result, Is.EqualTo((long)expectedResult));
        }

        [Test]
        public void PassingAndReturningADoubleShouldReturnADecimal()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            double expectedResult = 1.2;
            object result = ExecuteScript("return arguments[0];", expectedResult);
            Assert.That(result, Is.InstanceOf<float>().Or.InstanceOf<double>());
            Assert.That(result, Is.EqualTo((double)expectedResult));
        }

        [Test]
        public void ShouldThrowAnExceptionWhenTheJavascriptIsBad()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = xhtmlTestPage;
            Assert.That(() => ExecuteScript("return squiggle();"), Throws.InstanceOf<WebDriverException>());
        }

        [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 ShouldThrowAnExceptionWithMessageAndStacktraceWhenTheJavascriptIsBad()
        {
            driver.Url = xhtmlTestPage;
            string js = "function functionB() { throw Error('errormessage'); };"
                        + "function functionA() { functionB(); };"
                        + "functionA();";

            Assert.That(
               () => ExecuteScript(js),
               Throws.InstanceOf<WebDriverException>()
               .With.Message.Contains("errormessage")
               .And.Property(nameof(WebDriverException.StackTrace)).Contains("functionB"));
        }

        [Test]
        public void ShouldBeAbleToCallFunctionsDefinedOnThePage()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;
            ExecuteScript("displayMessage('I like cheese');");
            string text = driver.FindElement(By.Id("result")).Text;

            Assert.That(text.Trim(), Is.EqualTo("I like cheese"));
        }

        [Test]
        public void ShouldBeAbleToPassAStringAsAnArgument()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            string text = (string)ExecuteScript("return arguments[0] == 'Hello!' ? 'Hello!' : 'Goodbye!';", "Hello!");
            Assert.That(text, Is.EqualTo("Hello!"));
        }

        [Test]
        public void ShouldBeAbleToPassABooleanAsAnArgument()
        {

            string function = "return arguments[0] == true ? true : false;";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            bool result = (bool)ExecuteScript(function, true);
            Assert.That(result, Is.True);

            result = (bool)ExecuteScript(function, false);
            Assert.That(result, Is.False);
        }

        [Test]
        public void ShouldBeAbleToPassANumberAsAnArgument()
        {
            string functionTemplate = "return arguments[0] == {0} ? {0} : 0;";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            string function = string.Format(functionTemplate, 3);
            long result = (long)ExecuteScript(function, 3);
            Assert.That(result, Is.EqualTo(3));

            function = string.Format(functionTemplate, -3);
            result = (long)ExecuteScript(function, -3);
            Assert.That(result, Is.EqualTo(-3));

            function = string.Format(functionTemplate, 2147483647);
            result = (long)ExecuteScript(function, 2147483647);
            Assert.That(result, Is.EqualTo(2147483647));

            function = string.Format(functionTemplate, -2147483647);
            result = (long)ExecuteScript(function, -2147483647);
            Assert.That(result, Is.EqualTo(-2147483647));
        }

        [Test]

        public void ShouldBeAbleToPassAWebElementAsArgument()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;
            IWebElement button = driver.FindElement(By.Id("plainButton"));
            string value = (string)ExecuteScript("arguments[0]['flibble'] = arguments[0].getAttribute('id'); return arguments[0]['flibble'];", button);

            Assert.That(value, Is.EqualTo("plainButton"));
        }

        [Test]
        public void PassingArrayAsOnlyArgumentShouldFlattenArray()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            object[] array = new object[] { "zero", 1, true, 3.14159 };
            long length = (long)ExecuteScript("return arguments[0].length", array);
            Assert.That(length, Is.EqualTo(array.Length));
        }

        [Test]
        public void ShouldBeAbleToPassAnArrayAsAdditionalArgument()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            object[] array = new object[] { "zero", 1, true, 3.14159, false };
            long length = (long)ExecuteScript("return arguments[1].length", "string", array);
            Assert.That(length, Is.EqualTo(array.Length));
        }

        [Test]
        public void ShouldBeAbleToPassACollectionAsArgument()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            List<object> collection = new List<object>();
            collection.Add("Cheddar");
            collection.Add("Brie");
            collection.Add(7);
            long length = (long)ExecuteScript("return arguments[0].length", collection);
            Assert.That(length, Is.EqualTo(collection.Count));
        }

        [Test]
        public void ShouldThrowAnExceptionIfAnArgumentIsNotValid()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            Assert.That(
                () => ExecuteScript("return arguments[0];", driver),
                Throws.ArgumentException.With.Message.StartsWith("Argument is of an illegal type: "));
        }

        [Test]
        public void ShouldBeAbleToPassInMoreThanOneArgument()
        {
            if (!(driver is IJavaScriptExecutor))
            {
                return;
            }

            driver.Url = javascriptPage;
            string result = (string)ExecuteScript("return arguments[0] + arguments[1];", "one", "two");

            Assert.That(result, Is.EqualTo("onetwo"));
        }

        [Test]
        public void ShouldBeAbleToGrabTheBodyOfFrameOnceSwitchedTo()
        {
            driver.Url = richTextPage;

            driver.SwitchTo().Frame("editFrame");
            IWebElement body = (IWebElement)((IJavaScriptExecutor)driver).ExecuteScript("return document.body");

            Assert.That(body.Text, Is.Empty);
        }

        // This is a duplicate test of ShouldBeAbleToExecuteScriptAndReturnElementsList.
        // It's here and commented only to make comparison with the Java language bindings
        // tests easier.
        //[Test]
        //public void testShouldBeAbleToReturnAnArrayOfWebElements()
        //{
        //    driver.Url = formsPage;

        //    ReadOnlyCollection<IWebElement> items = (ReadOnlyCollection<IWebElement>)((IJavaScriptExecutor)driver)
        //        .ExecuteScript("return document.getElementsByName('snack');");

        //    Assert.That(items.Count, Is.Not.EqualTo(0));
        //}

        [Test]
        public void JavascriptStringHandlingShouldWorkAsExpected()
        {
            driver.Url = javascriptPage;

            string value = (string)ExecuteScript("return '';");
            Assert.That(value, Is.Empty);

            value = (string)ExecuteScript("return undefined;");
            Assert.That(value, Is.Null);

            value = (string)ExecuteScript("return ' '");
            Assert.That(value, Is.EqualTo(" "));
        }

        [Test]
        public void ShouldBeAbleToExecuteABigChunkOfJavascriptCode()
        {
            driver.Url = javascriptPage;
            string path = System.IO.Path.Combine(Environment.EnvironmentManager.Instance.CurrentDirectory, ".." + System.IO.Path.DirectorySeparatorChar + "..");
            string[] fileList = System.IO.Directory.GetFiles(path, "jquery-1.2.6.min.js", System.IO.SearchOption.AllDirectories);
            if (fileList.Length > 0)
            {
                string jquery = System.IO.File.ReadAllText(fileList[0]);
                Assert.That(jquery, Has.Length.GreaterThan(50000));
                ExecuteScript(jquery, null);
            }
        }

        [Test]
        [IgnoreBrowser(Selenium.Browser.IE, "IE does not support Chrome DevTools Protocol")]
        [IgnoreBrowser(Selenium.Browser.Firefox, "Firefox does not support Chrome DevTools Protocol")]
        [IgnoreBrowser(Selenium.Browser.Safari, "Safari does not support Chrome DevTools Protocol")]
        public async Task ShouldBeAbleToPinJavascriptCodeAndExecuteRepeatedly()
        {
            IJavaScriptEngine jsEngine = new JavaScriptEngine(driver);

            driver.Url = xhtmlTestPage;

            PinnedScript script = await jsEngine.PinScript("return document.title;");
            for (int i = 0; i < 5; i++)
            {
                object result = ((IJavaScriptExecutor)driver).ExecuteScript(script);

                Assert.That(result, Is.InstanceOf<string>());
                Assert.That(result, Is.EqualTo("XHTML Test Page"));
            }

            await jsEngine.UnpinScript(script);

            Assert.That(
                () => ((IJavaScriptExecutor)driver).ExecuteScript(script),
                Throws.TypeOf<JavaScriptException>());
        }

        [Test]
        public void ShouldBeAbleToExecuteScriptAndReturnElementsList()
        {
            driver.Url = formsPage;
            String scriptToExec = "return document.getElementsByName('snack');";

            object resultObject = ((IJavaScriptExecutor)driver).ExecuteScript(scriptToExec);

            ReadOnlyCollection<IWebElement> resultsList = (ReadOnlyCollection<IWebElement>)resultObject;

            Assert.That(resultsList, Is.Not.Empty);
        }

        [Test]
        public void ShouldBeAbleToCreateAPersistentValue()
        {
            driver.Url = formsPage;

            ExecuteScript("document.alerts = []");
            ExecuteScript("document.alerts.push('hello world');");
            string text = (string)ExecuteScript("return document.alerts.shift()");

            Assert.That(text, Is.EqualTo("hello world"));
        }

        [Test]
        public void ShouldBeAbleToHandleAnArrayOfElementsAsAnObjectArray()
        {
            driver.Url = formsPage;

            ReadOnlyCollection<IWebElement> forms = driver.FindElements(By.TagName("form"));
            object[] args = new object[] { forms };

            string name = (string)((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0][0].tagName", args);

            Assert.That(name, Is.EqualTo("form").IgnoreCase);
        }

        [Test]
        public void ShouldBeAbleToPassADictionaryAsAParameter()
        {
            driver.Url = simpleTestPage;

            List<int> nums = new List<int>() { 1, 2 };
            Dictionary<string, object> args = new Dictionary<string, object>();
            args["bar"] = "test";
            args["foo"] = nums;

            object res = ((IJavaScriptExecutor)driver).ExecuteScript("return arguments[0]['foo'][1]", args);

            Assert.That((long)res, Is.EqualTo(2));
        }

        [Test]
        public void ShouldThrowAnExceptionWhenArgumentsWithStaleElementPassed()
        {
            IJavaScriptExecutor executor = driver as IJavaScriptExecutor;
            if (executor == null)
            {
                return;
            }

            driver.Url = simpleTestPage;

            IWebElement el = driver.FindElement(By.Id("oneline"));

            driver.Url = simpleTestPage;

            Dictionary<string, object> args = new Dictionary<string, object>();
            args["key"] = new object[] { "a", new object[] { "zero", 1, true, 3.14159, false, el }, "c" };
            Assert.That(
                () => executor.ExecuteScript("return undefined;", args),
                Throws.TypeOf<StaleElementReferenceException>());
        }

        [Test]
        [IgnoreBrowser(Browser.Chrome, "Browser does not return Date object.")]
        [IgnoreBrowser(Browser.Edge, "Browser does not return Date object.")]
        public void ShouldBeAbleToReturnADateObject()
        {
            driver.Url = simpleTestPage;

            string date = (string)ExecuteScript("return new Date();");
            DateTime.Parse(date);
        }

        [Test]
        [IgnoreBrowser(Browser.Chrome, "Driver returns object that allows getting text.")]
        [IgnoreBrowser(Browser.Edge, "Driver returns object that allows getting text.")]
        [IgnoreBrowser(Browser.Firefox, "Driver does not return the documentElement object.")]
        [IgnoreBrowser(Browser.IE, "Driver does not return the documentElement object.")]
        [IgnoreBrowser(Browser.Safari, "Driver does not return the documentElement object.")]
        public void ShouldReturnDocumentElementIfDocumentIsReturned()
        {
            driver.Url = simpleTestPage;

            object value = ExecuteScript("return document");

            Assert.That(value, Is.InstanceOf<IWebElement>());
            Assert.That(((IWebElement)value).Text, Does.Contain("A single line of text"));
        }

        [Test]
        public void ShouldHandleObjectThatThatHaveToJSONMethod()
        {
            driver.Url = simpleTestPage;

            object value = ExecuteScript("return window.performance.timing");

            Assert.That(value, Is.InstanceOf<Dictionary<string, object>>());
        }

        [Test]
        public void ShouldHandleRecursiveStructures()
        {
            driver.Url = simpleTestPage;

            Assert.That(
                () => ExecuteScript(
                    """
                    var obj1 = {};
                    var obj2 = {};
                    obj1['obj2'] = obj2;
                    obj2['obj1'] = obj1;
                    return obj1
                    """
                    ),
                Throws.TypeOf<JavaScriptException>());
        }

        //------------------------------------------------------------------
        // Tests below here are not included in the Java test suite
        //------------------------------------------------------------------
        [Test]
        [NeedsFreshDriver(IsCreatedBeforeTest = true, IsCreatedAfterTest = true)]
        [Ignore("Reason for ignore: Failure indicates hang condition, which would break the test suite. Really needs a timeout set.")]
        public void ShouldThrowExceptionIfExecutingOnNoPage()
        {
            Assert.That(
                () => ((IJavaScriptExecutor)driver).ExecuteScript("return 1;"),
                Throws.InstanceOf<WebDriverException>());
        }

        [Test]
        public void ExecutingLargeJavaScript()
        {
            string script = """
                // stolen from injectableSelenium.js in WebDriver
                var browserbot = {

                    triggerEvent: function(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
                        canBubble = (typeof(canBubble) == undefined) ? true : canBubble;

                        if (element.fireEvent && element.ownerDocument && element.ownerDocument.createEventObject) {
                            // IE
                            var evt = this.createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
                            element.fireEvent('on' + eventType,evt);
                        } else {
                            var evt = document.createEvent('HTMLEvents');

                            try {
                                evt.shiftKey = shiftKeyDown;
                                evt.metaKey = metaKeyDown;
                                evt.altKey = altKeyDown;
                                evt.ctrlKey = controlKeyDown;
                            } catch(e) {
                                // Nothing sane to do
                            }

                            evt.initEvent(eventType, canBubble, true);
                            return element.dispatchEvent(evt);
                        }
                    },

                    getVisibleText: function() {
                        var selection = getSelection();
                        var range = document.createRange();
                        range.selectNodeContents(document.documentElement);
                        selection.addRange(range);

                        var string = selection.toString();
                        selection.removeAllRanges();

                        return string;
                    },

                    getOuterHTML: function(element) {
                        if(element.outerHTML) {
                            return element.outerHTML;
                        } else if(typeof(XMLSerializer) != undefined) {
                            return new XMLSerializer().serializeToString(element);
                        } else {
                            throw "can't get outerHTML in this browser";
                        }
                    }
                };

                return browserbot.getOuterHTML.apply(browserbot, arguments);
                """;

            driver.Url = javascriptPage;
            IWebElement element = driver.FindElement(By.TagName("body"));
            object x = ExecuteScript(script, element);
        }

        [Test]

        public void ShouldBeAbleToPassMoreThanOneStringAsArguments()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;
            string text = (string)ExecuteScript("return arguments[0] + arguments[1] + arguments[2] + arguments[3];", "Hello,", " ", "world", "!");

            Assert.That(text, Is.EqualTo("Hello, world!"));
        }

        [Test]
        public void ShouldBeAbleToPassMoreThanOneBooleanAsArguments()
        {

            string function = "return (arguments[0] ? 'True' : 'False') + (arguments[1] ? 'True' : 'False');";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            string text = (string)ExecuteScript(function, true, true);
            Assert.That(text, Is.EqualTo("TrueTrue"));

            text = (string)ExecuteScript(function, false, true);
            Assert.That(text, Is.EqualTo("FalseTrue"));

            text = (string)ExecuteScript(function, true, false);
            Assert.That(text, Is.EqualTo("TrueFalse"));

            text = (string)ExecuteScript(function, false, false);
            Assert.That(text, Is.EqualTo("FalseFalse"));
        }

        [Test]
        public void ShouldBeAbleToPassMoreThanOneNumberAsArguments()
        {
            string function = "return arguments[0]+arguments[1];";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            long result = (long)ExecuteScript(function, 30, 12);
            Assert.That(result, Is.EqualTo(42));

            result = (long)ExecuteScript(function, -30, -12);
            Assert.That(result, Is.EqualTo(-42));

            result = (long)ExecuteScript(function, 2147483646, 1);
            Assert.That(result, Is.EqualTo(2147483647));

            result = (long)ExecuteScript(function, -2147483646, -1);
            Assert.That(result, Is.EqualTo(-2147483647));

        }

        [Test]
        public void ShouldBeAbleToPassADoubleAsAnArgument()
        {
            string function = "return arguments[0];";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            double result = (double)ExecuteScript(function, (double)4.2);
            Assert.That(result, Is.EqualTo(4.2));

            result = (double)ExecuteScript(function, (double)-4.2);
            Assert.That(result, Is.EqualTo(-4.2));

            result = (double)ExecuteScript(function, (float)4.2);
            Assert.That(result, Is.EqualTo(4.2));

            result = (double)ExecuteScript(function, (float)-4.2);
            Assert.That(result, Is.EqualTo(-4.2));

            result = (long)ExecuteScript(function, (double)4.0);
            Assert.That(result, Is.EqualTo(4));

            result = (long)ExecuteScript(function, (double)-4.0);
            Assert.That(result, Is.EqualTo(-4));
        }

        [Test]
        public void ShouldBeAbleToPassMoreThanOneDoubleAsArguments()
        {
            String function = "return arguments[0]+arguments[1];";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            double result = (double)ExecuteScript(function, 30.1, 12.1);
            Assert.That(result, Is.EqualTo(42.2));

            result = (double)ExecuteScript(function, -30.1, -12.1);
            Assert.That(result, Is.EqualTo(-42.2));

            result = (double)ExecuteScript(function, 2147483646.1, 1.0);
            Assert.That(result, Is.EqualTo(2147483647.1));

            result = (double)ExecuteScript(function, -2147483646.1, -1.0);
            Assert.That(result, Is.EqualTo(-2147483647.1));

        }

        [Test]
        public void ShouldBeAbleToPassMoreThanOneWebElementAsArguments()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;
            IWebElement button = driver.FindElement(By.Id("plainButton"));
            IWebElement dynamo = driver.FindElement(By.Id("dynamo"));
            string value = (string)ExecuteScript("arguments[0]['flibble'] = arguments[0].getAttribute('id'); return arguments[0]['flibble'] + arguments[1].innerHTML;", button, dynamo);

            Assert.That(value, Is.EqualTo("plainButtonWhat's for dinner?"));
        }

        [Test]
        public void ShouldBeAbleToPassInMixedArguments()
        {
            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            IWebElement dynamo = driver.FindElement(By.Id("dynamo"));
            string result = (string)ExecuteScript("return arguments[0].innerHTML + arguments[1].toString() + arguments[2].toString() + arguments[3] + arguments[4]",
                dynamo,
                42,
                4.2,
                "Hello, World!",
                true);

            Assert.That(result, Is.EqualTo("What's for dinner?424.2Hello, World!true"));

        }

        [Test]
        public void ShouldBeAbleToPassInAndRetrieveDates()
        {
            string function = "displayMessage(arguments[0]);";

            if (!(driver is IJavaScriptExecutor))
                return;

            driver.Url = javascriptPage;

            ExecuteScript(function, "2014-05-20T20:00:00+08:00");
            IWebElement element = driver.FindElement(By.Id("result"));
            string text = element.Text;
            Assert.That(text, Is.EqualTo("2014-05-20T20:00:00+08:00"));
        }

        private object ExecuteScript(String script, params Object[] args)
        {
            object toReturn = ((IJavaScriptExecutor)driver).ExecuteScript(script, args);
            return toReturn;
        }
    }
}
