Skip to content
Open
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
111 changes: 111 additions & 0 deletions .github/workflows/doc-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
name: Documentation examples

on:
pull_request:
branches: [master]

permissions:
contents: read

concurrency:
group: doc-examples-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
coverage:
name: Examples coverage
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22.21.1
cache: npm
- run: npm ci
- run: npm run test:examples:unit
- name: Check new and changed examples against the PR base
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: npm run check:examples -- --base "$BASE_SHA"
- name: Publish coverage summary
if: ${{ always() }}
run: |
if [ -f tmp/doc-examples/summary.md ]; then
cat tmp/doc-examples/summary.md >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload full inventory including legacy debt
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: doc-examples-coverage
path: tmp/doc-examples/
if-no-files-found: warn
retention-days: 14

runtime:
name: Examples runtime (${{ matrix.suite }})
runs-on: ubuntu-24.04
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
suite: [chrome, selenium]
defaults:
run:
working-directory: vibe/verified-examples
shell: bash
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-node@v4
with:
node-version: 22.21.1
cache: npm
cache-dependency-path: vibe/verified-examples/package-lock.json
- uses: actions/setup-java@v4
if: ${{ matrix.suite == 'selenium' }}
with:
distribution: temurin
java-version: "21"
- name: Cache Testplane browsers and drivers
uses: actions/cache@v4
with:
path: ~/.testplane
key: doc-examples-${{ runner.os }}-${{ runner.arch }}-chrome150-${{ hashFiles('vibe/verified-examples/package-lock.json', 'vibe/verified-examples/testplane.config.js', 'vibe/verified-examples/run-ci.cjs') }}
- run: npm ci --legacy-peer-deps
- name: Download pinned Selenium Grid
if: ${{ matrix.suite == 'selenium' }}
run: |
mkdir -p artifacts
curl --fail --location --retry 3 \
https://github.com/SeleniumHQ/selenium/releases/download/selenium-4.50.0/selenium-server-4.50.0.jar \
--output "$RUNNER_TEMP/selenium-server.jar"
echo "SELENIUM_JAR=$RUNNER_TEMP/selenium-server.jar" >> "$GITHUB_ENV"
- name: Run every declared example and verify runtime results
env:
SUITE: ${{ matrix.suite }}
run: |
mkdir -p artifacts
node run-ci.cjs "$SUITE" 2>&1 | tee artifacts/testplane.log
- name: Publish runtime summary
if: ${{ always() }}
run: |
if [ -f artifacts/runtime-summary.md ]; then
cat artifacts/runtime-summary.md >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload results even on failure
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: doc-examples-${{ matrix.suite }}
path: |
vibe/verified-examples/artifacts/
vibe/verified-examples/validation-environment.json
vibe/verified-examples/testplane-debug/
if-no-files-found: warn
retention-days: 14
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ package-lock.json
storybook-static
build
tmp

# Generated example-validation output
vibe/verified-examples/artifacts/
vibe/verified-examples/validation-environment.json
vibe/verified-examples/testplane-debug/
vibe/verified-examples/.testplane/
4 changes: 4 additions & 0 deletions docs/basic-guides/selectors.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ Use this approach if:
- you need access to elements inside the shadow root
- you are working with a design system based on Web Components

## Custom locator strategies {/* #custom-locator-strategies */}

If standard selectors are insufficient, register a strategy with [browser.addLocatorStrategy](../commands/browser/addLocatorStrategy.mdx). Use [browser.custom$](../commands/browser/customDollar.mdx) for one element and [browser.custom$$](../commands/browser/customDollarDollar.mdx) for multiple elements. To search within an element, use [element.custom$](../commands/element/customDollar.mdx) or [element.custom$$](../commands/element/customDollarDollar.mdx). The strategy runs in the browser, so pass data as arguments rather than through Node.js closures. The command pages contain verified examples.

## Testing Library

Testing Library allows you to find elements the way users do — by text, element type, or other attributes that don't depend on your layout details.
Expand Down
120 changes: 65 additions & 55 deletions docs/basic-guides/user-environment-emulation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -327,60 +327,65 @@ it("applies styles for the dark color scheme", async ({ browser }) => {
});
```

## Network
## Network {/* #network */}

### Offline mode
### Offline mode {/* #offline-mode */}

To test how the application works without a network connection, use [`browser.throttleNetwork("offline")`][throttle-network].
Use [throttleNetwork][throttle-network] to block actual requests. The example requires Chromium with CDP/Puppeteer access, one tab, `isolation: false` and `testsPerSession: 1`: the implementation selects the first Puppeteer page. Navigate before disabling the network.

For example, you can test the error message displayed when a network request fails:
In the verified Testplane 9.3.2 / WDIO 9.6.2 / Chrome 150 combination, the `online` profile did not restore connectivity after `offline`. The example therefore ends the offline session and checks connectivity in a new one rather than promising recovery through that call. Point `baseUrl` to a test HTTP server that returns `200` at the root path.

```typescript
it("shows a message when there is no network connection", async ({ browser }) => {
await browser.url("/");
```javascript
const assert = require("node:assert/strict");

// Configure testsPerSession: 1 so this offline session is not reused.
it("throttleNetwork: check a failed request while offline", async ({ browser }) => {
await browser.url("/");
await browser.throttleNetwork("offline");
const failed = await browser.execute(async () => {
try {
await fetch("/?offline-check", { cache: "no-store" });
return false;
} catch {
return true;
}
});
assert.equal(failed, true);
});

const loadOrders = await browser.getByTestId("load-orders");
await loadOrders.click();

const networkError = await browser.findByTestId("network-error");
await expect(networkError).toHaveText("No network connection");

await browser.throttleNetwork("online");
it("throttleNetwork: the next session starts online", async ({ browser }) => {
await browser.url("/");
const status = await browser.execute(async () => {
const response = await fetch("/?online-check", { cache: "no-store" });
return response.status;
});
assert.equal(status, 200);
});
```

Load the page first, then disable the network before the action that sends a request. Unlike `emulate()`, `throttleNetwork()` must be called after navigation. To restore the normal network mode, use the `"online"` profile.
### Slow connection {/* #slow-connection */}

### Slow connection
Pass an object to [throttleNetwork][throttle-network] to limit throughput. The example uses the same isolation requirements. The server at `baseUrl` must serve HTML at `/` and JSON with `source: "original"` at `/api/items`.

To test the interface over a slow connection, pass network parameters to [`browser.throttleNetwork()`][throttle-network]:

```typescript
it("shows the loading state on a slow network", async ({ browser }) => {
await browser.url("/orders");
```javascript
const assert = require("node:assert/strict");

it("slow network: fetch data under custom throughput limits", async ({ browser }) => {
await browser.url("/");
await browser.throttleNetwork({
offline: false,
latency: 500,
downloadThroughput: (50 * 1024) / 8,
uploadThroughput: (20 * 1024) / 8,
});

const loadOrders = await browser.getByTestId("load-orders");
await loadOrders.click();

const loading = await browser.findByTestId("loading");
await expect(loading).toBeDisplayed();

await browser.throttleNetwork("online");
const data = await browser.execute(async () =>
(await fetch("/api/items", { cache: "no-store" })).json(),
);
assert.equal(data.source, "original");
});
```

The object contains four fields: `offline`, the `latency` in milliseconds, and the `downloadThroughput` and `uploadThroughput` rates in bytes per second.

For common network conditions, you do not have to specify the parameters manually. Pass the name of a predefined profile instead of an object, such as `"Good3G"` or `"offline"`.
`offline` enables offline mode, `latency` is in milliseconds, and `downloadThroughput` and `uploadThroughput` are in bytes per second. You can pass a preset such as `Good3G` instead of an object. The throttled session is not reused by the next test.

### navigator.onLine

Expand All @@ -400,25 +405,30 @@ The emulation persists until the end of the session, so restore it in the same t

`browser.emulate("onLine", false)` only changes the value of `navigator.onLine`; it does not disable the network, and HTTP requests continue to work.

## CPU performance
## CPU performance {/* #cpu-performance */}

To test the interface with limited CPU performance, use [`browser.throttleCPU()`][throttle-cpu].
[throttleCPU][throttle-cpu] slows down code execution in Chromium through CDP/Puppeteer. As with network throttling, use one tab and `isolation: false`: the command targets the first Puppeteer page. Navigate before calling it. Set `baseUrl` to a test HTML page.

For example, you can test a scenario with 4× CPU slowdown as follows:
The example applies a 2× slowdown and removes it in `finally`.

```typescript
it("works with a throttled CPU", async ({ browser }) => {
await browser.throttleCPU(4);
```javascript
const assert = require("node:assert/strict");

it("throttleCPU: run a page with a slower CPU", async ({ browser }) => {
await browser.url("/");

// ...

await browser.throttleCPU(1);
try {
await browser.throttleCPU(2);
await browser.execute(() => {
document.body.innerHTML = "<button>Ready</button>";
});
assert.equal(await browser.$("button").getText(), "Ready");
} finally {
await browser.throttleCPU(1);
}
});
```

The higher the slowdown factor, the slower the code runs. A value of `1` disables throttling.
A larger factor slows code execution further. A value of `1` disables throttling.

## Geolocation

Expand Down Expand Up @@ -551,26 +561,26 @@ describe("dark color scheme", () => {

If a setting must remain active for the entire session, create a separate browser configuration for it. This approach is more convenient for setting the browser language, running tests with JavaScript disabled, and setting a fixed window size with [`windowSize`][window-size].

[emulate]: https://webdriver.io/docs/api/browser/emulate
[emulate]: ../commands/browser/emulate.mdx
[webdriver-bidi]: https://w3c.github.io/webdriver-bidi/
[set-viewport]: https://webdriver.io/docs/api/browser/setViewport
[set-viewport]: ../commands/browser/setViewport.mdx
[set-window-size]: ../commands/browser/setWindowSize.mdx
[window-size]: ../reference/config/browsers.mdx#window_size
[how-to-use-cdp]: ../guides/how-to-use-cdp.mdx
[emulate-device]: https://webdriver.io/docs/emulation#device
[emulate-user-agent]: https://webdriver.io/docs/emulation#user-agent
[emulate-device]: ../commands/browser/emulate.mdx#parameters
[emulate-user-agent]: ../commands/browser/emulate.mdx#parameters
[get-puppeteer]: ../commands/browser/getPuppeteer.mdx
[page-set-user-agent]: https://pptr.dev/api/puppeteer.page.setuseragent
[cdp-set-locale-override]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setLocaleOverride
[page-emulate-timezone]: https://pptr.dev/api/puppeteer.page.emulatetimezone
[emulate-clock]: https://webdriver.io/docs/emulation#clock
[clock-tick]: https://webdriver.io/docs/api/clock/tick
[emulate-color-scheme]: https://webdriver.io/docs/emulation#color-scheme
[emulate-clock]: ../commands/browser/emulate.mdx#parameters
[clock-tick]: ../commands/browser/emulate.mdx#result
[emulate-color-scheme]: ../commands/browser/emulate.mdx#parameters
[cdp-set-emulated-media]: https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setEmulatedMedia
[throttle-network]: https://webdriver.io/docs/api/browser/throttleNetwork
[emulate-online]: https://webdriver.io/docs/emulation#online-property
[throttle-cpu]: https://webdriver.io/docs/api/browser/throttleCPU
[emulate-geolocation]: https://webdriver.io/docs/emulation#geolocation
[throttle-network]: ../commands/browser/throttleNetwork.mdx
[emulate-online]: ../commands/browser/emulate.mdx#parameters
[throttle-cpu]: ../commands/browser/throttleCPU.mdx
[emulate-geolocation]: ../commands/browser/emulate.mdx#parameters
[set-permissions]: https://webdriver.io/docs/api/webdriver#setpermissions
[restore]: https://webdriver.io/docs/api/browser/restore
[restore]: ../commands/browser/restore.mdx
[testing-library-guide]: ../guides/how-to-add-testing-library.mdx
56 changes: 56 additions & 0 deletions docs/commands/browser/addInitScript.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# addInitScript

## Overview {/* #overview */}

Run a function in new documents of the current tab or window before page scripts execute. Use this to prepare browser-side values before the application reads them. The function also runs in newly attached or navigated child frames within that top-level browsing context.

The script is registered for the tab or window that is current when `addInitScript` is called. It does not automatically apply to other tabs or windows, including those opened later. To use it there, switch to the target tab or window and register it separately before navigation.

## Usage {/* #usage */}

`browser.addInitScript(script, ...args)`

## Parameters {/* #parameters */}

| **Parameter** | **Type** | **Default** | **Description** |
| ------------- | ----------- | ----------- | ------------------------------------------------------------------------ |
| `script` | `Function` | Required | A function executed in the browser, without access to Node.js variables. |
| `args` | `unknown[]` | [] | Arguments serialized as JSON and passed to the function. |

## Result {/* #result */}

`Promise<InitScript>`: an object with asynchronous `remove()` and `on("data", listener)`. To send data to Node.js, call the `emit` callback appended to the script arguments.

## Limitations {/* #limitations */}

Requires a WebDriver BiDi session (`webSocketUrl: true`). Call the command before navigation. `remove()` prevents future injection; it does not undo changes already made in the current document. Remove the script in the same test, even if an assertion fails. In this version the implementation accepts a function, not a string.

## Example {/* #examples */}

Set `baseUrl` to your local HTTP test site, where `/` returns an HTML page. Run the example in a regular Testplane test file.

```javascript
const assert = require("node:assert/strict");

it("addInitScript: set a value before page scripts", async ({ browser }) => {
const script = await browser.addInitScript(value => {
window.exampleSeed = value;
}, 42);

try {
await browser.url("/");
assert.equal(await browser.execute(() => window.exampleSeed), 42);
} finally {
await script.remove();
}

await browser.url("/");
assert.equal(await browser.execute(() => typeof window.exampleSeed), "undefined");
});
```

## See also {/* #see-also */}

- [execute](./execute.mdx)
- [url](./url.mdx)
- [emulate](./emulate.mdx)
Loading
Loading