Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/selenium-devtools/example/cucumber-test/cucumber.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"default": {
"import": [
"example/cucumber-test/features/support/setup.js",
"example/cucumber-test/features/support/world.js",
"example/cucumber-test/features/support/steps.js"
],
"paths": ["example/cucumber-test/features/*.feature"],
"publishQuiet": true,
"format": ["progress"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: the-internet login flow

Scenario: logs in with valid credentials and lands on /secure
Given I am on the login page
When I enter username "tomsmith" and password "SuperSecretPassword!"
And I submit the login form
Then I should be on the secure page
And I should see a flash message matching "You logged into a secure area"

Scenario: rejects invalid username with an error flash
Given I am on the login page
When I enter username "foobar" and password "barfoo"
And I submit the login form
Then I should see a flash message matching "Your username is invalid"
And I should still be on the login page
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Loads the @wdio/selenium-devtools plugin and configures it.
*
* Run from the package root: pnpm example:cucumber
*/

import { DevTools } from '@wdio/selenium-devtools'

DevTools.configure({
screencast: { enabled: true, quality: 70, maxWidth: 1280, maxHeight: 720 },
headless: true
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { strict as assert } from 'node:assert'
import { Given, When, Then } from '@cucumber/cucumber'
import { By, until } from 'selenium-webdriver'

Given('I am on the login page', async function () {
await this.driver.get('https://the-internet.herokuapp.com/login')
})

When(
'I enter username {string} and password {string}',
async function (username, password) {
await this.driver.findElement(By.id('username')).sendKeys(username)
await this.driver.findElement(By.id('password')).sendKeys(password)
}
)

When('I submit the login form', async function () {
await this.driver.findElement(By.css('button[type="submit"]')).click()
})

Then('I should be on the secure page', async function () {
await this.driver.wait(until.urlContains('/secure'), 10_000)
})

Then(
'I should see a flash message matching {string}',
async function (pattern) {
const flash = await this.driver.wait(
until.elementLocated(By.id('flash')),
10_000
)
const text = await flash.getText()
assert.match(text, new RegExp(pattern, 'i'))
await this.driver.sleep(1500)
}
)

Then('I should still be on the login page', async function () {
const url = await this.driver.getCurrentUrl()
assert.match(url, /\/login$/)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { setWorldConstructor, World, Before, After, setDefaultTimeout } from '@cucumber/cucumber'
import { Builder } from 'selenium-webdriver'

setDefaultTimeout(60_000)

class CustomWorld extends World {
constructor(options) {
super(options)
this.driver = null
}
}

setWorldConstructor(CustomWorld)

Before(async function () {
this.driver = await new Builder().forBrowser('chrome').build()
})

After(async function () {
if (this.driver) {
await this.driver.quit()
this.driver = null
}
})
6 changes: 6 additions & 0 deletions packages/selenium-devtools/example/jest-test/jest.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"testEnvironment": "node",
"testMatch": ["<rootDir>/test/*.test.js"],
"testTimeout": 60000,
"transform": {}
}
60 changes: 60 additions & 0 deletions packages/selenium-devtools/example/jest-test/test/example.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Login flow against the-internet.herokuapp.com under Jest.
*
* Run from the package root: pnpm example:jest
*/

import { DevTools } from '@wdio/selenium-devtools'

Check failure on line 7 in packages/selenium-devtools/example/jest-test/test/example.test.js

View workflow job for this annotation

GitHub Actions / build

packages/selenium-devtools/example/jest-test/test/example.test.js

Error: Failed to resolve entry for package "@wdio/selenium-devtools". The package may have incorrect main/module/exports specified in its package.json. ❯ packages/selenium-devtools/example/jest-test/test/example.test.js:7:1
import { Builder, By, until } from 'selenium-webdriver'

const LOGIN_URL = 'https://the-internet.herokuapp.com/login'
const VALID_USERNAME = 'tomsmith'
const VALID_PASSWORD = 'SuperSecretPassword!'

DevTools.configure({
screencast: { enabled: true, quality: 70, maxWidth: 1280, maxHeight: 720 },
headless: true
})

describe('the-internet login flow', () => {
let driver

beforeEach(async () => {
driver = await new Builder().forBrowser('chrome').build()
}, 60000)

afterEach(async () => {
if (driver) {
await driver.quit()
}
})

test('logs in with valid credentials and lands on /secure', async () => {
await driver.get(LOGIN_URL)
await driver.findElement(By.id('username')).sendKeys(VALID_USERNAME)
await driver.findElement(By.id('password')).sendKeys(VALID_PASSWORD)
await driver.findElement(By.css('button[type="submit"]')).click()

await driver.wait(until.urlContains('/secure'), 10000)
const flash = await driver.wait(until.elementLocated(By.id('flash')), 10000)
const flashText = await flash.getText()
expect(flashText).toMatch(/You logged into a secure area/i)

await driver.sleep(1500)
}, 60000)

test('rejects invalid username with an error flash', async () => {
await driver.get(LOGIN_URL)
await driver.findElement(By.id('username')).sendKeys('foobar')
await driver.findElement(By.id('password')).sendKeys('barfoo')
await driver.findElement(By.css('button[type="submit"]')).click()

const flash = await driver.wait(until.elementLocated(By.id('flash')), 10000)
const flashText = await flash.getText()
expect(flashText).toMatch(/Your username is invalid/i)
const url = await driver.getCurrentUrl()
expect(url).toMatch(/\/login$/)

await driver.sleep(1500)
}, 60000)
})
43 changes: 43 additions & 0 deletions packages/selenium-devtools/example/mocha-test/test/example.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Smoke test for @wdio/selenium-devtools.
*
* Run from the package root: pnpm example:mocha
*/

import { strict as assert } from 'node:assert'
import { Builder, By, until } from 'selenium-webdriver'

Check failure on line 8 in packages/selenium-devtools/example/mocha-test/test/example.test.js

View workflow job for this annotation

GitHub Actions / build

packages/selenium-devtools/example/mocha-test/test/example.test.js

Error: Cannot find package 'selenium-webdriver' imported from /home/runner/work/devtools/devtools/packages/selenium-devtools/example/mocha-test/test/example.test.js ❯ packages/selenium-devtools/example/mocha-test/test/example.test.js:8:1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }
import { DevTools } from '@wdio/selenium-devtools'

DevTools.configure({
screencast: { enabled: true, quality: 70, maxWidth: 1280, maxHeight: 720 },
headless: true
})

describe('selenium-devtools smoke test', function () {
let driver

before(async function () {
driver = await new Builder().forBrowser('chrome').build()
})

after(async function () {
if (driver) {
await driver.quit()
}
})

it('loads example.com and reads the heading', async function () {
await driver.get('https://example.com')
await driver.sleep(1500)
const heading = await driver.wait(until.elementLocated(By.css('h1')), 10000)
const text = await heading.getText()
assert.equal(text, 'Example Domain')
})

it('navigates and reads the page title', async function () {
await driver.get('https://example.org')
await driver.sleep(1500)
const title = await driver.getTitle()
assert.match(title, /Example/i)
})
})
7 changes: 7 additions & 0 deletions packages/selenium-devtools/example/vitest-test/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { DevTools } from '@wdio/selenium-devtools'

DevTools.configure({
rerunCommand: 'npx vitest --testNamePattern "{{testName}}"',
screencast: { enabled: true, quality: 70, maxWidth: 1280, maxHeight: 720 },
headless: true
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Login flow against the-internet.herokuapp.com under Vitest.
*
* Run from the package root: pnpm example:vitest
*/

import { Builder, By, until } from 'selenium-webdriver'

Check failure on line 7 in packages/selenium-devtools/example/vitest-test/test/example.test.js

View workflow job for this annotation

GitHub Actions / build

packages/selenium-devtools/example/vitest-test/test/example.test.js

Error: Cannot find package 'selenium-webdriver' imported from /home/runner/work/devtools/devtools/packages/selenium-devtools/example/vitest-test/test/example.test.js ❯ packages/selenium-devtools/example/vitest-test/test/example.test.js:7:1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { code: 'ERR_MODULE_NOT_FOUND' }

const LOGIN_URL = 'https://the-internet.herokuapp.com/login'
const VALID_USERNAME = 'tomsmith'
const VALID_PASSWORD = 'SuperSecretPassword!'

describe('the-internet login flow', () => {
let driver

beforeEach(async () => {
driver = await new Builder().forBrowser('chrome').build()
})

afterEach(async () => {
if (driver) {
await driver.quit()
}
})

test('logs in with valid credentials and lands on /secure', async () => {
await driver.get(LOGIN_URL)
await driver.findElement(By.id('username')).sendKeys(VALID_USERNAME)
await driver.findElement(By.id('password')).sendKeys(VALID_PASSWORD)
await driver.findElement(By.css('button[type="submit"]')).click()

await driver.wait(until.urlContains('/secure'), 10000)
const flash = await driver.wait(until.elementLocated(By.id('flash')), 10000)
const flashText = await flash.getText()
expect(flashText).toMatch(/You logged into a secure area/i)

await driver.sleep(1500)
})

test('rejects invalid username with an error flash', async () => {
await driver.get(LOGIN_URL)
await driver.findElement(By.id('username')).sendKeys('foobar')
await driver.findElement(By.id('password')).sendKeys('barfoo')
await driver.findElement(By.css('button[type="submit"]')).click()

const flash = await driver.wait(until.elementLocated(By.id('flash')), 10000)
const flashText = await flash.getText()
expect(flashText).toMatch(/Your username is invalid/i)
const url = await driver.getCurrentUrl()
expect(url).toMatch(/\/login$/)

await driver.sleep(1500)
})
})
16 changes: 16 additions & 0 deletions packages/selenium-devtools/example/vitest-test/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineConfig } from 'vitest/config'

export default defineConfig({
test: {
globals: true,
testTimeout: 60000,
hookTimeout: 60000,
include: ['example/vitest-test/test/**/*.test.js'],
setupFiles: ['./example/vitest-test/setup.js'],
// Single fork keeps one DevTools backend across files. The plugin
// patches selenium-webdriver at module load — running in worker threads
// would multiplex that against shared imports.
pool: 'forks',
poolOptions: { forks: { singleFork: true } }
}
})
68 changes: 68 additions & 0 deletions packages/selenium-devtools/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@wdio/selenium-devtools",
"version": "1.0.0",
"description": "Selenium WebDriver adapter for WebdriverIO DevTools — runner-agnostic, reuses existing backend, UI, and capture infrastructure",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/webdriverio/devtools.git",
"directory": "packages/selenium-devtools"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js"
}
},
"files": [
"dist",
"README.md"
],
"scripts": {
"build": "tsc",
"watch": "tsc --watch",
"clean": "rm -rf dist",
"lint": "eslint .",
"example:mocha": "mocha --require @wdio/selenium-devtools --timeout 60000 example/mocha-test/test/*.test.js",
"example:jest": "NODE_OPTIONS=--experimental-vm-modules jest --config example/jest-test/jest.config.json",
"example:vitest": "vitest run --config example/vitest-test/vitest.config.js",
"example:cucumber": "cucumber-js --config example/cucumber-test/cucumber.json"
},
"keywords": [
"selenium",
"selenium-webdriver",
"devtools",
"debugging",
"testing"
],
"author": "WebdriverIO Team",
"license": "MIT",
"dependencies": {
"@wdio/devtools-backend": "workspace:*",
"@wdio/devtools-script": "workspace:*",
"@wdio/logger": "^9.6.0",
"stacktrace-parser": "^0.1.11",
"webdriverio": "^9.18.0",
"ws": "^8.18.3"
},
"optionalDependencies": {
"fluent-ffmpeg": "^2.1.3"
},
"devDependencies": {
"@cucumber/cucumber": "^11.1.0",
"@types/node": "25.5.2",
"@types/ws": "^8.18.1",
"chromedriver": "^147.0.1",
"jest": "^29.7.0",
"mocha": "^10.7.0",
"selenium-webdriver": "^4.27.0",
"typescript": "^6.0.2",
"vitest": "^2.1.9"
},
"peerDependencies": {
"selenium-webdriver": ">=4.8.0"
}
}
Loading
Loading