| <!DOCTYPE HTML> |
| <html> |
| <script src="test.js"></script> |
| <script src="call_function.js"></script> |
| <script> |
| function wrapArgs(value, w3c) { |
| if (typeof w3c === "undefined") { |
| w3c = true; |
| } |
| const nodes = []; |
| const preprocessed = preprocessResult(value, [], nodes); |
| const stringified = JSON.stringify(preprocessed); |
| return [JSON.parse(stringified), w3c, nodes]; |
| } |
| |
| function unwrapResult(value) { |
| return resolveReferences(JSON.parse(value[0]), value.slice(1)); |
| } |
| |
| function serialize(value) { |
| const nodes = []; |
| const preprocessed = preprocessResult(value, [], nodes); |
| return [JSON.stringify(preprocessed), ...nodes]; |
| } |
| |
| function deserialize(serialized) { |
| return resolveReferences(JSON.parse(serialized[0]), serialized.slice(1)); |
| } |
| |
| function roundtrip(value) { |
| return deserialize(serialize(value)); |
| } |
| |
| function testSerialize() { |
| assertEquals(1, roundtrip(1)); |
| assertEquals("1", roundtrip("1")); |
| assertEquals("chromium", roundtrip("chromium")); |
| assertEquals(false, roundtrip(false)); |
| assertEquals(null, roundtrip(null)); |
| assertEquals(undefined, roundtrip(undefined)); |
| assertEquals(JSON.stringify([]), JSON.stringify(roundtrip([]))); |
| assertEquals(JSON.stringify(["uno", 2]), JSON.stringify(roundtrip(["uno", 2]))); |
| assertEquals(JSON.stringify({}), JSON.stringify(roundtrip({}))); |
| assertEquals(JSON.stringify({}), |
| JSON.stringify(roundtrip(function(){console.log("Hello, World!");}))); |
| |
| const div = document.querySelector("div"); |
| assertEquals(div, roundtrip(div)); |
| assertEquals(document, roundtrip(document)); |
| |
| const divs = document.querySelectorAll("div"); |
| const deserialized = roundtrip(divs); |
| assertEquals(divs[0], deserialized[0]); |
| assertEquals(divs[1], deserialized[1]); |
| } |
| |
| function testDeepSerialization() { |
| const original = [1, new Array(1, new Object({a: 1, b: {a: 1, b: {}, c: 3}}), 3)]; |
| const originalJson = JSON.stringify(original); |
| |
| original[1][1].b.b = document; |
| let deserialized = roundtrip(original); |
| assertEquals(document, deserialized[1][1].b.b); |
| |
| const div = document.querySelector("div"); |
| original[1][1].b.b = div; |
| deserialized = roundtrip(original); |
| assertEquals(div, deserialized[1][1].b.b); |
| |
| deserialized[1][1].b.b = {}; |
| assertEquals(originalJson, JSON.stringify(deserialized)); |
| } |
| |
| function testObjectWithLengthProperty() { |
| let obj = {length: 200}; |
| assertEquals(200, roundtrip({length: 200})["length"]) |
| assertEquals(JSON.stringify(obj), JSON.stringify(roundtrip({length: 200}))) |
| |
| obj = {bar: "foo", bazz: {length: 150}}; |
| const deserialized = roundtrip(obj); |
| assertEquals("foo", deserialized["bar"]) |
| assertEquals(150, deserialized["bazz"]["length"]) |
| assertEquals(JSON.stringify(obj), JSON.stringify(deserialized)); |
| } |
| |
| function testDeserializeElementReference() { |
| const original = {}; |
| original[ELEMENT_KEY] = 0; |
| const json = JSON.stringify(original); |
| deserialized = deserialize([json, document]); |
| assertEquals(deserialized, document); |
| } |
| |
| function testDeserializeNegativeIndexThrows() { |
| const original = {}; |
| original[ELEMENT_KEY] = -1; |
| const json = JSON.stringify(original); |
| let thrown = false; |
| try { |
| deserialize([json, document]); |
| } catch (e) { |
| thrown = true; |
| } |
| assert(thrown); |
| } |
| |
| function testDeserializeIndexOutOfRange() { |
| const original = {}; |
| original[ELEMENT_KEY] = 1; |
| const json = JSON.stringify(original); |
| let thrown = false; |
| try { |
| deserialize([json, document]); |
| } catch (e) { |
| thrown = true; |
| } |
| assert(thrown); |
| } |
| |
| function testStaleRef() { |
| const img = document.createElement("img"); |
| document.body.appendChild(img); |
| const serialized = serialize(img); |
| document.body.removeChild(img); |
| let thrown = false; |
| try { |
| deserialize(serialized); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, e.code); |
| } |
| assert(thrown); |
| } |
| |
| function testDeserializeWithNodeIndexOutOfRange() { |
| const img = document.createElement("img"); |
| document.body.appendChild(img); |
| const serialized = [serialize(img)[0]]; |
| let thrown = false; |
| try { |
| deserialize(serialized); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.JAVA_SCRIPT_ERROR, e.code); |
| } |
| assert(thrown); |
| } |
| |
| function testSerializationWithShadowDomAttached() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with attached element in shadow DOM. |
| const deserialized = roundtrip(shadowDiv); |
| assertEquals(shadowDiv, deserialized); |
| |
| document.body.removeChild(host); |
| } |
| |
| function testSerializeDetachedElementInShadowTree() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| root.removeChild(shadowDiv); |
| let thrown = false; |
| try { |
| serialize(shadowDiv); |
| document.body.removeChild(host); |
| } catch (e) { |
| thrown = true; |
| document.body.removeChild(host); |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| function testDeserializeDetachedElementInShadowTree() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| const serialized = serialize(shadowDiv); |
| root.removeChild(shadowDiv); |
| let thrown = false; |
| try { |
| deserialize(serialized); |
| document.body.removeChild(host); |
| } catch (e) { |
| thrown = true; |
| document.body.removeChild(host); |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| function testSerializeElementInDetachedShadowTree() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| document.body.removeChild(host); |
| let thrown = false; |
| try { |
| serialize(shadowDiv); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| function testDeserializeElementInDetachedShadowTree() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| const serialized = serialize(shadowDiv); |
| document.body.removeChild(host); |
| let thrown = false; |
| try { |
| deserialize(serialized); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| function testSerializeDetachedShadowTreeRoot() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| document.body.removeChild(host); |
| let thrown = false; |
| try { |
| serialize(root); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.DETACHED_SHADOW_ROOT, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| function testDeserializeDetachedShadowTreeRoot() { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| // Test with detached element in shadow DOM. |
| const serialized = serialize(root); |
| document.body.removeChild(host); |
| let thrown = false; |
| try { |
| deserialize(serialized); |
| } catch (e) { |
| thrown = true; |
| assertEquals(StatusCode.DETACHED_SHADOW_ROOT, e.code); |
| } |
| |
| assert(thrown) |
| } |
| |
| // Verify array serialization works when Array.prototype.toJSON is defined. |
| function testSerializeArrayToJsonProto(runner) { |
| Array.prototype.toJSON = () => "proto"; |
| const original = [1, 7, "tres"]; |
| const result = roundtrip(original); |
| delete Array.prototype.toJSON; |
| assert(result instanceof Array); |
| assertEquals(result[0], 1); |
| assertEquals(result[1], 7); |
| assertEquals(result[2], "tres"); |
| assertEquals(JSON.stringify(original), JSON.stringify(result)); |
| } |
| |
| function testSerializeArrayToJsonOwn(runner) { |
| const original = [10, 7, "tres"]; |
| original.toJSON = () => "own"; |
| const result = roundtrip(original); |
| assertEquals(result, "own"); |
| assertEquals(JSON.stringify(original), JSON.stringify(result)); |
| } |
| |
| function testSerializeArrayToJsonOwnAndProto(runner) { |
| Array.prototype.toJSON = () => "proto"; |
| const original = [10, 33, "tres"]; |
| original.toJSON = () => "own"; |
| const result = roundtrip(original); |
| delete Array.prototype.toJSON; |
| assertEquals(result, "own"); |
| assertEquals(JSON.stringify(original), JSON.stringify(result)); |
| } |
| |
| function testDeepSerializationCustomToJson() { |
| const fancy = [4, 16, 64]; |
| fancy.toJSON = () => "fancy-tojson"; |
| const original = [1, Array(1, new Object({a: [3, 27], b: {a: 1, b: {}, c: fancy}}), 3)]; |
| const originalJson = JSON.stringify(original); |
| const div = document.querySelector("div"); |
| original[1][1].b.b = div; |
| |
| Array.prototype.toJSON = () => "proto"; |
| deserialized = roundtrip(original); |
| delete Array.prototype.toJSON; |
| |
| assertEquals(div, deserialized[1][1].b.b); |
| assertEquals("fancy-tojson", deserialized[1][1].b.c); |
| deserialized[1][1].b.b = {}; |
| assertEquals(originalJson, JSON.stringify(deserialized)); |
| } |
| |
| function testDeepSerializationCustomToJsonNestedArrays() { |
| const fancy = [4, [5, 55], 64]; |
| fancy.toJSON = () => "fancy-tojson"; |
| const original = [111, fancy, "third"]; |
| const originalJson = JSON.stringify(original); |
| |
| Array.prototype.toJSON = () => "proto"; |
| deserialized = roundtrip(original); |
| delete Array.prototype.toJSON; |
| |
| assertEquals(111, deserialized[0]); |
| assertEquals("fancy-tojson", deserialized[1]); |
| assertEquals("third", deserialized[2]); |
| assertEquals(originalJson, JSON.stringify(deserialized)); |
| } |
| |
| function testCallFunctionNoArgs(runner) { |
| callFunction(function() { return 1; }, [], true, []).then((resultArray) => { |
| const value = unwrapResult(resultArray).value; |
| assertEquals(1, value); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallFunctionThrows(runner) { |
| let allComplete = 0; |
| callFunction(function() { throw new Error("fake error"); }, |
| [], true, []).then((result) => { |
| const unwrapped = unwrapResult(result); |
| assertEquals(StatusCode.JAVA_SCRIPT_ERROR, unwrapped.status); |
| assertEquals("fake error", unwrapped.value); |
| if (allComplete) |
| runner.continueTesting(); |
| allComplete += 1; |
| }); |
| callFunction(function() { |
| const e = new Error("fake error"); |
| e.code = 77; |
| e.message = "CUSTOM"; |
| throw e; |
| }, [], true, []).then((result) => { |
| const unwrapped = unwrapResult(result); |
| assertEquals(StatusCode.JAVA_SCRIPT_ERROR, unwrapped.status); |
| const nestedError = JSON.parse(unwrapped.value); |
| assertEquals(77, nestedError.status); |
| assertEquals("CUSTOM", nestedError.value); |
| if (allComplete) |
| runner.continueTesting(); |
| allComplete += 1; |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallWithMalformedIdThrows() { |
| const original = {}; |
| original[ELEMENT_KEY] = "malformed_id"; |
| callFunction(function(){}, ...wrapArgs([original])).then((resultArray) => { |
| const unwrapped = unwrapResult(result); |
| assertEquals(StatusCode.STALE_ELEMENT_REFERENCE, unwrapped.status); |
| assertEquals("stale element not found", unwrapped.value); |
| }); |
| } |
| |
| function testCallFunctionArgs(runner) { |
| function func(primitive, elem) { |
| return [primitive, elem.querySelector("div")]; |
| } |
| callFunction(func, ...wrapArgs([1, document])).then((resultArray) => { |
| const values = unwrapResult(resultArray).value; |
| assertEquals(1, values[0]); |
| assertEquals(document.querySelector("div"), values[1]); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallFunctionArgsNonW3C(runner) { |
| function func(elem) { |
| return elem.querySelector("div"); |
| } |
| callFunction(func, ...wrapArgs([document], w3c=false)).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(document.querySelector("div"), result); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallFunctionWithShadowHost(runner) { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| function func(element) { |
| return element; |
| } |
| callFunction(func, ...wrapArgs([host])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(host, result); |
| document.body.removeChild(host); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallFunctionWithShadowRoot(runner) { |
| // Set up something in the shadow DOM. |
| const host = document.body.appendChild(document.createElement("div")); |
| const root = host.attachShadow({ mode: "open" }); |
| const shadowDiv = root.appendChild(document.createElement("div")); |
| |
| function func(element) { |
| return element; |
| } |
| // Should handle shadow root as an argument. |
| callFunction(func, ...wrapArgs([root])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(root, result); |
| document.body.removeChild(host); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| // Verify callFunction works when Object.prototype has user-defined functions. |
| // (https://crbug.com/chromedriver/3074) |
| function testCallWithFunctionInObject(runner) { |
| Object.prototype.f = () => {}; |
| function func(arg) { |
| return { bar: arg }; |
| } |
| callFunction(func, ...wrapArgs([{ foo: 1 }])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(1, result.bar.foo); |
| delete Object.prototype.f; |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| // Verify array serialization works when Array.prototype.toJSON is defined. |
| // (https://crbug.com/chromedriver/3084) |
| function testCallWithArrayToJsonProto(runner) { |
| Array.prototype.toJSON = () => "[\"testing\"]"; |
| function func() { |
| return [111, "dos", 3]; |
| } |
| callFunction(func, ...wrapArgs([])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assert(result instanceof Array); |
| assertEquals(result.length, 3); |
| assertEquals(result[0], 111); |
| assertEquals(result[1], 'dos'); |
| assertEquals(result[2], 3); |
| delete Array.prototype.toJSON; |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| // Verify array serialization works when own property toJSON is defined. |
| // (https://crbug.com/chromedriver/4325) |
| function testCallWithArrayToJsonOwn(runner) { |
| function func() { |
| const result = [1, 2, 3]; |
| result.toJSON = () => "[\"testing\"]"; |
| return result; |
| } |
| callFunction(func, ...wrapArgs([])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(typeof result, "string"); |
| assertEquals(result, "[\"testing\"]"); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallWithArrayToJsonOwnAndProto(runner) { |
| Array.prototype.toJSON = () => "proto"; |
| function func() { |
| const result = [1, 2, 3]; |
| result.toJSON = () => "own"; |
| return result; |
| } |
| callFunction(func, ...wrapArgs([])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(typeof result, "string"); |
| assertEquals(result, "own"); |
| delete Array.prototype.toJSON; |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallWithModifiedObjectProto(runner) { |
| let callCount = 0; |
| Object.defineProperty(Object.prototype, "f", { |
| enumerable: true, |
| configurable: true, |
| get: function() { |
| callCount += 1; |
| } |
| }); |
| |
| callFunction(function() {}, []).then(() => { |
| try { |
| assertEquals(callCount, 0); |
| runner.continueTesting(); |
| } finally { |
| delete Object.prototype.f; |
| } |
| }); |
| runner.waitForAsync(); |
| } |
| |
| function testCallFunctionWithLargeData(runner) { |
| const original = "0".repeat(10e6); |
| function func(arg) { |
| return arg; |
| } |
| callFunction(func, ...wrapArgs([original])).then((resultArray) => { |
| const result = unwrapResult(resultArray).value; |
| assertEquals(result, original); |
| runner.continueTesting(); |
| }); |
| runner.waitForAsync(); |
| } |
| </script> |
| <body> |
| <div><span>div1</span></div> |
| <div><span>div2</span></div> |
| </body> |
| </html> |