From 5794cd06f9d884584fa5821e9609e111713279d7 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Mon, 22 Jul 2024 18:33:54 +0530 Subject: [PATCH 1/4] [bidi][js] Add high-level script command --- .../selenium-webdriver/bidi/protocolValue.js | 86 ++++- .../node/selenium-webdriver/lib/script.js | 16 + .../test/bidi/local_value_test.js | 6 +- .../test/lib/webdriver_script_execute_test.js | 311 ++++++++++++++++++ 4 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js diff --git a/javascript/node/selenium-webdriver/bidi/protocolValue.js b/javascript/node/selenium-webdriver/bidi/protocolValue.js index 059621b792e9e..4bfb27aa221ab 100644 --- a/javascript/node/selenium-webdriver/bidi/protocolValue.js +++ b/javascript/node/selenium-webdriver/bidi/protocolValue.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -const { PrimitiveType, NonPrimitiveType, RemoteType } = require('./protocolType') +const { PrimitiveType, NonPrimitiveType, RemoteType, SpecialNumberType } = require('./protocolType') const TYPE_CONSTANT = 'type' const VALUE_CONSTANT = 'value' @@ -189,6 +189,88 @@ class LocalValue { return new ReferenceValue(handle, sharedId) } + static getArgument(argument) { + let localValue = null + + if ( + argument === SpecialNumberType.NAN || + argument === SpecialNumberType.MINUS_ZERO || + argument === SpecialNumberType.INFINITY || + argument === SpecialNumberType.MINUS_INFINITY + ) { + localValue = LocalValue.createSpecialNumberValue(argument) + return localValue + } + + const type = typeof argument + + switch (type) { + case PrimitiveType.STRING: + localValue = LocalValue.createStringValue(argument) + break + case PrimitiveType.NUMBER: + localValue = LocalValue.createNumberValue(argument) + break + case PrimitiveType.BOOLEAN: + localValue = LocalValue.createBooleanValue(argument) + break + case PrimitiveType.BIGINT: + localValue = LocalValue.createBigIntValue(argument.toString()) + break + case PrimitiveType.UNDEFINED: + localValue = LocalValue.createUndefinedValue() + break + case NonPrimitiveType.OBJECT: + if (argument === null) { + localValue = LocalValue.createNullValue() + break + } + if (argument instanceof Date) { + localValue = LocalValue.createDateValue(argument) + } else if (argument instanceof Map) { + const map = [] + + argument.forEach((value, key) => { + let objectKey + if (typeof key === 'string') { + objectKey = key + } else { + objectKey = LocalValue.getArgument(key) + } + const objectValue = LocalValue.getArgument(value) + map.push([objectKey, objectValue]) + }) + localValue = new LocalValue(NonPrimitiveType.MAP, map) + } else if (argument instanceof Set) { + const set = [] + argument.forEach((value) => { + set.push(LocalValue.getArgument(value)) + }) + localValue = LocalValue.createSetValue(set) + } else if (argument instanceof Array) { + const arr = [] + argument.forEach((value) => { + arr.push(LocalValue.getArgument(value)) + }) + localValue = LocalValue.createArrayValue(arr) + } else if (argument instanceof RegExp) { + localValue = LocalValue.createRegularExpressionValue({ + pattern: argument.source, + flags: argument.flags, + }) + } else { + let value = [] + Object.entries(argument).forEach((entry) => { + value.push([LocalValue.getArgument(entry[0]), LocalValue.getArgument(entry[1])]) + }) + localValue = new LocalValue(NonPrimitiveType.OBJECT, value) + } + break + } + + return localValue + } + asMap() { let toReturn = {} toReturn[TYPE_CONSTANT] = this.type @@ -246,7 +328,7 @@ class RemoteValue { } deserializeValue(value, type) { - if ([NonPrimitiveType.MAP, NonPrimitiveType.OBJECT].includes(type)) { + if (type === NonPrimitiveType.OBJECT) { return Object.fromEntries(value) } else if (type === NonPrimitiveType.REGULAR_EXPRESSION) { return new RegExpValue(value.pattern, value.flags) diff --git a/javascript/node/selenium-webdriver/lib/script.js b/javascript/node/selenium-webdriver/lib/script.js index 2a64dd03e7d56..beaa80257de5b 100644 --- a/javascript/node/selenium-webdriver/lib/script.js +++ b/javascript/node/selenium-webdriver/lib/script.js @@ -121,6 +121,22 @@ class Script { await this.#initScript() await this.#script.removePreloadScript(id) } + + async execute(script, ...args) { + await this.#initScript() + + const browsingContextId = await this.#driver.getWindowHandle() + + const argumentList = [] + + args.forEach((arg) => { + argumentList.push(LocalValue.getArgument(arg)) + }) + + const response = await this.#script.callFunctionInBrowsingContext(browsingContextId, script, true, argumentList) + + return response.result + } } module.exports = Script diff --git a/javascript/node/selenium-webdriver/test/bidi/local_value_test.js b/javascript/node/selenium-webdriver/test/bidi/local_value_test.js index d7bc7c4eeef5c..94c4f48bdc82e 100644 --- a/javascript/node/selenium-webdriver/test/bidi/local_value_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/local_value_test.js @@ -350,9 +350,9 @@ suite( let resultValue = result.result.value - assert.equal(Object.keys(resultValue).length, 1) - assert.equal(resultValue['foobar'].type, 'string') - assert.equal(resultValue['foobar'].value, 'foobar') + assert.equal(resultValue[0][0], 'foobar') + assert.equal(resultValue[0][1].type, 'string') + assert.equal(resultValue[0][1].value, 'foobar') }) it('can call function with object argument', async function () { diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js new file mode 100644 index 0000000000000..79253b2db8d7c --- /dev/null +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js @@ -0,0 +1,311 @@ +// 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. + +'use strict' + +const assert = require('node:assert') +const { Browser } = require('selenium-webdriver/') +const { suite } = require('../../lib/test') + +const ScriptManager = require('selenium-webdriver/bidi/scriptManager') +const { LocalValue, RegExpValue } = require('selenium-webdriver/bidi/protocolValue') +const { SpecialNumberType } = require('selenium-webdriver/bidi/protocolType') + +suite( + function (env) { + let driver + + beforeEach(async function () { + driver = await env.builder().build() + }) + + afterEach(async function () { + await driver.quit() + }) + + describe('Execute script', function () { + it('can execute script with undefined argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==undefined)\n' + + ' throw Error("Argument should be undefined, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + undefined, + ) + + assert.equal(result.type, 'undefined') + }) + + it('can execute script with null argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==null)\n' + + ' throw Error("Argument should be null, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + null, + ) + + assert.equal(result.type, 'null') + }) + it('can execute script with minus zero argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==-0)\n' + + ' throw Error("Argument should be -0, but was " + arg);\n' + + ' return arg;\n' + + ' }}', + SpecialNumberType.MINUS_ZERO, + ) + + assert.equal(result.type, 'number') + assert.equal(result.value, '-0') + }) + + it('can execute script with infinity argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==Infinity)\n' + + ' throw Error("Argument should be Infinity, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + SpecialNumberType.INFINITY, + ) + + assert.equal(result.type, 'number') + assert.equal(result.value, 'Infinity') + }) + + it('can execute script with minus infinity argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==-Infinity)\n' + + ' throw Error("Argument should be -Infinity, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + SpecialNumberType.MINUS_INFINITY, + ) + + assert.equal(result.type, 'number') + assert.equal(result.value, '-Infinity') + }) + + it('can execute script with number argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==1.4)\n' + + ' throw Error("Argument should be 1.4, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + 1.4, + ) + + assert.equal(result.type, 'number') + assert.equal(result.value, 1.4) + }) + + it('can execute script with boolean argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==true)\n' + + ' throw Error("Argument should be true, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + true, + ) + + assert.equal(result.type, 'boolean') + assert.equal(result.value, true) + }) + + it('can execute script with big int argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(arg!==42n)\n' + + ' throw Error("Argument should be 42n, but was "+arg);\n' + + ' return arg;\n' + + ' }}', + 42n, + ) + + assert.equal(result.type, 'bigint') + assert.equal(result.value, '42') + }) + + it('can execute script with array argument', async function () { + let arrayValue = ['foobar'] + + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof Array))\n' + + ' throw Error("Argument type should be Array, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + arrayValue, + ) + + assert.equal(result.type, 'array') + + let resultValue = result.value + assert.equal(resultValue.length, 1) + assert.equal(resultValue[0].type, 'string') + assert.equal(resultValue[0].value, 'foobar') + }) + + it('can execute script with set argument', async function () { + let setValues = new Set() + setValues.add('foobar') + setValues.add('test') + + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof Set))\n' + + ' throw Error("Argument type should be Set, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + setValues, + ) + + assert.equal(result.type, 'set') + + let resultValue = result.value + assert.equal(resultValue.length, 2) + assert.equal(resultValue[0].type, 'string') + assert.equal(resultValue[0].value, 'foobar') + assert.equal(resultValue[1].type, 'string') + assert.equal(resultValue[1].value, 'test') + }) + + it('can execute script with date argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof Date))\n' + + ' throw Error("Argument type should be Date, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + new Date('2022-05-31T13:47:29.000Z'), + ) + + assert.equal(result.type, 'date') + assert.equal(result.value, '2022-05-31T13:47:29.000Z') + }) + + it('can execute script with map argument', async function () { + let mapValue = new Map() + mapValue.set(1, 2) + mapValue.set('foo', 'bar') + mapValue.set(true, false) + mapValue.set('baz', [1, 2, 3]) + + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof Map))\n' + + ' throw Error("Argument type should be Map, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + mapValue, + ) + assert.equal(result.type, 'map') + assert.notEqual(result.value, null) + + let resultValue = result.value + + assert.equal(resultValue.length, 4) + + assert.equal( + JSON.stringify(resultValue), + '[[{"type":"number","value":1},{"type":"number","value":2}],["foo",{"type":"string","value":"bar"}],[{"type":"boolean","value":true},{"type":"boolean","value":false}],["baz",{"type":"array","value":[{"type":"number","value":1},{"type":"number","value":2},{"type":"number","value":3}]}]]', + ) + }) + + it('can execute script with object argument', async function () { + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof Object))\n' + + ' throw Error("Argument type should be Object, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + { foobar: 'foobar' }, + ) + + assert.equal(result.type, 'object') + + let resultValue = result.value + assert.equal(resultValue['foobar'].type, 'string') + assert.equal(resultValue['foobar'].value, 'foobar') + }) + + it('can execute script with regex argument', async function () { + const id = await driver.getWindowHandle() + const manager = await ScriptManager(id, driver) + let argumentValues = [] + let value = LocalValue.createRegularExpressionValue(new RegExpValue('foo', 'g')) + argumentValues.push(value) + + const result = await driver + .script() + .execute( + '(arg) => {{\n' + + ' if(! (arg instanceof RegExp))\n' + + ' throw Error("Argument type should be RegExp, but was "+\n' + + ' Object.prototype.toString.call(arg));\n' + + ' return arg;\n' + + ' }}', + new RegExp('foo', 'g'), + ) + + let resultValue = result.value + + assert.equal(resultValue.pattern, 'foo') + assert.equal(resultValue.flags, 'g') + }) + }) + }, + { browsers: [Browser.FIREFOX, Browser.CHROME, Browser.EDGE] }, +) From d8ac776f9e823fe59df7ad0414257ab9ccadf2c6 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Wed, 24 Jul 2024 10:27:51 +0530 Subject: [PATCH 2/4] Fix formatting --- javascript/node/selenium-webdriver/lib/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/node/selenium-webdriver/lib/script.js b/javascript/node/selenium-webdriver/lib/script.js index beaa80257de5b..04b8fac06787a 100644 --- a/javascript/node/selenium-webdriver/lib/script.js +++ b/javascript/node/selenium-webdriver/lib/script.js @@ -121,7 +121,7 @@ class Script { await this.#initScript() await this.#script.removePreloadScript(id) } - + async execute(script, ...args) { await this.#initScript() From b4b18d7f9f02c37e6ad7a4409e3d5675b7b105e8 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Wed, 24 Jul 2024 11:41:55 +0530 Subject: [PATCH 3/4] Fix tests --- .../node/selenium-webdriver/test/bidi/locate_nodes_test.js | 5 +++-- .../test/lib/webdriver_script_execute_test.js | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js b/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js index a2736b5f40fab..109f2e07ad00e 100644 --- a/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js +++ b/javascript/node/selenium-webdriver/test/bidi/locate_nodes_test.js @@ -201,9 +201,10 @@ suite( assert.equal(response.resultType, EvaluateResultType.SUCCESS) assert.equal(response.result.type, 'map') - const sharedId = response.result.value.sharedId + const value = response.result.value[0] - assert.strictEqual(sharedId.value, nodeId) + assert.strictEqual(value[1].type, 'string') + assert.strictEqual(value[1].value, nodeId) }) it('can find element', async function () { diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js index 79253b2db8d7c..af415463b60bd 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js @@ -1,3 +1,4 @@ + // 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 From b05d283b66c89809bf8873b31bcf078f16724ae5 Mon Sep 17 00:00:00 2001 From: Puja Jagani Date: Wed, 31 Jul 2024 13:24:19 +0530 Subject: [PATCH 4/4] Fix linting --- .../selenium-webdriver/test/lib/webdriver_script_execute_test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js index af415463b60bd..79253b2db8d7c 100644 --- a/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js +++ b/javascript/node/selenium-webdriver/test/lib/webdriver_script_execute_test.js @@ -1,4 +1,3 @@ - // 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