Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: Porting Classic commands to BiDi in JS #13389

Open
wants to merge 9 commits into
base: trunk
Choose a base branch
from
8 changes: 5 additions & 3 deletions javascript/node/selenium-webdriver/firefox.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const zip = require('./io/zip')
const { Browser, Capabilities } = require('./lib/capabilities')
const { Zip } = require('./io/zip')
const { getPath } = require('./common/driverFinder')
const BiDiDelegator = require("./lib/bidiDelegator");
const FIREFOX_CAPABILITY_KEY = 'moz:firefoxOptions'

/**
Expand Down Expand Up @@ -467,9 +468,10 @@ const ExtensionCommand = {
*/
function createExecutor (serverUrl) {
let client = serverUrl.then((url) => new http.HttpClient(url))
let executor = new http.Executor(client)
configureExecutor(executor)
return executor

let httpExecutor = new http.Executor(client)
configureExecutor(httpExecutor)
return new BiDiDelegator(httpExecutor)
}

/**
Expand Down
17 changes: 16 additions & 1 deletion javascript/node/selenium-webdriver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const BrowsingContext = require('./bidi/browsingContext')
const BrowsingContextInspector = require('./bidi/browsingContextInspector')
const ScriptManager = require('./bidi/scriptManager')
const NetworkInspector = require('./bidi/networkInspector')
const {BiDiExecutor, getBiDiExecutorInstance} = require("./lib/bidiExecutor");

const Browser = capabilities.Browser
const Capabilities = capabilities.Capabilities
Expand Down Expand Up @@ -136,8 +137,21 @@ function createDriver(ctor, ...args) {
constructor(session, ...rest) {
super(session, ...rest)

let driver
const pd = this.getSession().then((session) => {
return new ctor(session, ...rest)
driver = new ctor(session, ...rest)
return this.getCapabilities()
}).then(caps => {
if (caps.get('webSocketUrl')) {
return getBiDiExecutorInstance(driver)
} else {
return driver
}
}).then(obj => {
if (obj instanceof BiDiExecutor) {
driver.getExecutor().setBidiExecutor(obj)
}
return driver
})

/** @override */
Expand Down Expand Up @@ -656,6 +670,7 @@ class Builder {
let client = Promise.resolve(url).then(
(url) => new _http.HttpClient(url, this.agent_, this.proxy_)
)
// Porting executor for the Grid
let executor = new _http.Executor(client)

if (browser === Browser.CHROME) {
Expand Down
46 changes: 46 additions & 0 deletions javascript/node/selenium-webdriver/lib/bidiDelegator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.

const {Executor} = require("./command");

const biDiCommands = new Set()
biDiCommands.add('get')
biDiCommands.add('printPage')

class BidiDelegator extends Executor {
#bidiExecutor
#httpExecutor

constructor(httpExecutor) {
super()
this.#httpExecutor = httpExecutor
}

setBidiExecutor(bidiExecutor) {
this.#bidiExecutor = bidiExecutor
}

execute(command) {
if (this.#bidiExecutor && biDiCommands.has(command.getName())) {
return this.#bidiExecutor.execute(command)
} else {
return this.#httpExecutor.execute(command)
}
}
}

module.exports = BidiDelegator
115 changes: 115 additions & 0 deletions javascript/node/selenium-webdriver/lib/bidiExecutor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 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.

const Executor = require('./command.js').Executor
const BrowsingContext = require('../bidi/browsingContext')

class BidiExecutor extends Executor {

#browsingContextMap = new Map()
#commandHandlers = new Map()
#driver
#currentContext

// Mapping the page load strategy values used in Classic To BiDi
// Refer: https://w3c.github.io/webdriver-bidi/#type-browsingContext-ReadinessState
// Refer: https://www.w3.org/TR/webdriver2/#navigation
#readinessStateMappings = [
["none", "none"],
["eager", "interactive"],
["normal", "complete"]]
#readinessStateMap = new Map(this.#readinessStateMappings)

constructor(driver) {
super()
this.#driver = driver
}

async init() {
let windowHandle = await this.#driver.getWindowHandle()

let parentContext = await BrowsingContext(this.#driver, {
browsingContextId: windowHandle,
})

this.#currentContext = parentContext
this.#browsingContextMap.set(windowHandle, parentContext)

this.#commandHandlers.set('get', async (driver, command) => {
let url = command.getParameter('url')
let caps = await this.#driver.getCapabilities()
let pageLoadStrategy = caps['map_'].get('pageLoadStrategy')
let response = await this.#currentContext.navigate(url, this.#readinessStateMap.get(pageLoadStrategy))

// Required because W3C Classic sets the current browsing context to current top-level
// context on navigation
// This is crucial for tests that:
// Switch to frame -> find element -> navigate url -> find element (in BiDi it will try to
// find an element in the frame)
// But in WebDriver Classic it will try to find the element on the page navigated to
// So to avoid breaking changes, we need to add this step
// Refer: Pt 9 of https://www.w3.org/TR/webdriver2/#navigate-to
// Refer: https://w3c.github.io/webdriver-bidi/#command-browsingContext-navigate

await driver.switchTo().window(this.#currentContext.id)
return response
})

this.#commandHandlers.set('printPage', async (driver, command) => {
let response = await this.#currentContext.printPage(command.getParameter('options'))
return response._data
})
}

async execute(command) {
let currentWindowHandle = await this.#driver.getWindowHandle()

let browsingContext
if (this.#browsingContextMap.has(currentWindowHandle)) {
browsingContext = this.#browsingContextMap.get(currentWindowHandle)
} else {
browsingContext = await BrowsingContext(this.#driver, {
browsingContextId: currentWindowHandle,
})
this.#browsingContextMap.set(currentWindowHandle, browsingContext)
}

this.#currentContext = browsingContext

if (this.#commandHandlers.has(command.getName())) {
let handler = this.#commandHandlers.get(command.getName())
let value = await handler(this.#driver, command)

// TODO: Return value in the expected format
return value
} else {
throw Error(`Command ${command.getName()} not found`)
}
}
}

async function getBiDiExecutorInstance(
driver) {
let instance = new BidiExecutor(driver)
await instance.init()
return instance
}

module.exports = {
getBiDiExecutorInstance,
BiDiExecutor: BidiExecutor,
}
1 change: 1 addition & 0 deletions javascript/node/selenium-webdriver/lib/webdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ class WebDriver {
})
}

// This needs to be updated based on BiDi support
/** @override */
actions(options) {
return new input.Actions(this, options || undefined)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// 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('assert')
const { By, Browser } = require('../../index')
const { Pages, ignore, suite } = require('../../lib/test')
const firefox = require("../../firefox");

suite(function (env) {
const browsers = (...args) => env.browsers(...args)
harsha509 marked this conversation as resolved.
Show resolved Hide resolved

let driver

beforeEach(async function () {
driver = await env
.builder()
.setFirefoxOptions(new firefox.Options().enableBidi())
.build()
})

afterEach(function () {
return driver.quit()
})

describe('Port Classic to BiDi Tests', function () {
it(
'getDomAttribute test',
async function () {
await driver.get(Pages.formPage)
const element = await driver.findElement(By.id('vsearchGadget'))
const attr = await element.getDomAttribute('accesskey')
assert.strictEqual(attr, '4')
}
)
})
},
{ browsers: [Browser.FIREFOX] }
)
Loading