Skip to content

Commit

Permalink
[js][BiDi] Browsing context commands (SeleniumHQ#11473)
Browse files Browse the repository at this point in the history
* [js][bidi] Browsing context commands

* added suggestions

Co-authored-by: Sri Harsha <12621691+harsha509@users.noreply.github.com>
  • Loading branch information
TamsilAmani and harsha509 authored Dec 23, 2022
1 parent c138787 commit de9a229
Show file tree
Hide file tree
Showing 3 changed files with 301 additions and 0 deletions.
194 changes: 194 additions & 0 deletions javascript/node/selenium-webdriver/bidi/browsingContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// 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.

class BrowsingContext {
constructor(driver) {
this._driver = driver
}

async init({ browsingContextId, type, referenceContext }) {
if (!(await this._driver.getCapabilities()).get('webSocketUrl')) {
throw Error('WebDriver instance must support BiDi protocol')
}

if (type != undefined && !['window', 'tab'].includes(type)) {
throw Error(`Valid types are 'window' & 'tab'. Received: ${type}`)
}

this.bidi = await this._driver.getBidi()
this._id =
browsingContextId == undefined
? (await this.create(type, referenceContext))['result']['context']
: browsingContextId
}

/**
* Creates a browsing context for the given type and referenceContext
*/
async create(type, referenceContext) {
const params = {
method: 'browsingContext.create',
params: {
type: type,
referenceContext: referenceContext,
},
}
return await this.bidi.send(params)
}

/**
* @returns id
*/
get id() {
return this._id
}

/**
* @param url the url to navigate to
* @param readinessState type of readiness state: "none" / "interactive" / "complete"
* @returns NavigateResult object
*/
async navigate(url, readinessState = undefined) {
if (
readinessState != undefined &&
!['none', 'interactive', 'complete'].includes(readinessState)
) {
throw Error(
`Valid readiness states are 'none', 'interactive' & 'complete'. Received: ${readinessState}`
)
}

const params = {
method: 'browsingContext.navigate',
params: {
context: this._id,
url: url,
wait: readinessState,
},
}
const navigateResult = (await this.bidi.send(params))['result']

return new NavigateResult(
navigateResult['url'],
navigateResult['navigation']
)
}

/**
* @param maxDepth the max depth of the descendents of browsing context tree
* @returns BrowsingContextInfo object
*/
async getTree(maxDepth = undefined) {
const params = {
method: 'browsingContext.getTree',
params: {
root: this._id,
maxDepth: maxDepth,
},
}

let result = await this.bidi.send(params)
if ('error' in result) {
throw Error(result['error'])
}

result = result['result']['contexts'][0]
return new BrowsingContextInfo(
result['context'],
result['url'],
result['children'],
result['parent']
)
}

/**
* Closes the browing context
* @returns {Promise<void>}
*/
async close() {
const params = {
method: 'browsingContext.close',
params: {
context: this._id,
},
}
await this.bidi.send(params)
}
}

class NavigateResult {
constructor(url, navigationId) {
this._url = url
this._navigationId = navigationId
}

get url() {
return this._url
}

get navigationId() {
return this._navigationId
}
}

class BrowsingContextInfo {
constructor(id, url, children, parentBrowsingContext) {
this._id = id
this._url = url
this._children = children
this._parentBrowsingContext = parentBrowsingContext
}

get id() {
return this._id
}

get url() {
return this._url
}

get children() {
return this._children
}

get parentBrowsingContext() {
return this._parentBrowsingContext
}
}

/**
* initiate browsing context instance and return
* @param driver
* @param browsingContextId The browsing context of current window/tab
* @param type "window" or "tab"
* @param referenceContext To get a browsing context for this reference if passed
* @returns {Promise<BrowsingContext>}
*/
async function getBrowsingContextInstance(
driver,
{ browsingContextId, type, referenceContext }
) {
let instance = new BrowsingContext(driver)
await instance.init({ browsingContextId, type, referenceContext })
return instance
}

/**
* API
* @type {function(*, {*,*,*}): Promise<BrowsingContext>}
*/
module.exports = getBrowsingContextInstance
2 changes: 2 additions & 0 deletions javascript/node/selenium-webdriver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const until = require('./lib/until')
const webdriver = require('./lib/webdriver')
const select = require('./lib/select')
const LogInspector = require('./bidi/logInspector')
const BrowsingContext = require('./bidi/browsingContext')

const Browser = capabilities.Browser
const Capabilities = capabilities.Capabilities
Expand Down Expand Up @@ -797,3 +798,4 @@ exports.promise = promise
exports.until = until
exports.Select = select.Select
exports.LogInspector = LogInspector
exports.BrowsingContext = BrowsingContext
105 changes: 105 additions & 0 deletions javascript/node/selenium-webdriver/test/bidi/bidi_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const firefox = require('../../firefox')
const { Browser } = require('../../')
const { Pages, suite } = require('../../lib/test')
const logInspector = require('../../bidi/logInspector')
const BrowsingContext = require('../../bidi/browsingContext')

suite(
function (env) {
Expand Down Expand Up @@ -104,6 +105,110 @@ suite(
await inspector.close()
})
})

describe('Browsing Context', function () {
it('can create a browsing context for given id', async function () {
const id = await driver.getWindowHandle()
const browsingContext = await BrowsingContext(driver, {
browsingContextId: id,
})
assert.equal(browsingContext.id, id)
})

it('can create a window', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'window',
})
assert.notEqual(browsingContext.id, null)
})

it('can create a window with a reference context', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'window',
referenceContext: await driver.getWindowHandle(),
})
assert.notEqual(browsingContext.id, null)
})

it('can create a tab', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'tab',
})
assert.notEqual(browsingContext.id, null)
})

it('can create a tab with a reference context', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'tab',
referenceContext: await driver.getWindowHandle(),
})
assert.notEqual(browsingContext.id, null)
})

it('can navigate to a url', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'tab',
})

let info = await browsingContext.navigate(Pages.logEntryAdded)

assert.notEqual(browsingContext.id, null)
assert.equal(info.navigationId, null)
assert(info.url.includes('/bidi/logEntryAdded.html'))
})

it('can navigate to a url with readiness state', async function () {
const browsingContext = await BrowsingContext(driver, {
type: 'tab',
})

const info = await browsingContext.navigate(
Pages.logEntryAdded,
'complete'
)

assert.notEqual(browsingContext.id, null)
assert.equal(info.navigationId, null)
assert(info.url.includes('/bidi/logEntryAdded.html'))
})

it('can get tree with a child', async function () {
const browsingContextId = await driver.getWindowHandle()
const parentWindow = await BrowsingContext(driver, {
browsingContextId: browsingContextId,
})
await parentWindow.navigate(Pages.iframePage, 'complete')

const contextInfo = await parentWindow.getTree()
assert.equal(contextInfo.children.length, 1)
assert.equal(contextInfo.id, browsingContextId)
assert(contextInfo.children[0]['url'].includes('formPage.html'))
})

it('can get tree with depth', async function () {
const browsingContextId = await driver.getWindowHandle()
const parentWindow = await BrowsingContext(driver, {
browsingContextId: browsingContextId,
})
await parentWindow.navigate(Pages.iframePage, 'complete')

const contextInfo = await parentWindow.getTree(0)
assert.equal(contextInfo.children, null)
assert.equal(contextInfo.id, browsingContextId)
})

it('can close a window', async function () {
const window1 = await BrowsingContext(driver, { type: 'window' })
const window2 = await BrowsingContext(driver, { type: 'window' })

await window2.close()

assert.doesNotThrow(async function () {
await window1.getTree()
})
await assert.rejects(window2.getTree(), { message: 'no such frame' })
})
})
},
{ browsers: [Browser.FIREFOX] }
)

0 comments on commit de9a229

Please sign in to comment.