diff --git a/examples/sync_colorscheme.js b/examples/sync_colorscheme.js
new file mode 100644
index 000000000..58269a903
--- /dev/null
+++ b/examples/sync_colorscheme.js
@@ -0,0 +1,45 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const preferredColorScheme = 'dark';
+
+ const context = browser.newContext({
+ // valid values are "light", "dark" or "no-preference"
+ colorScheme: preferredColorScheme,
+ });
+ const page = context.newPage();
+
+ try {
+ await page.goto(
+ 'https://googlechromelabs.github.io/dark-mode-toggle/demo/',
+ { waitUntil: 'load' },
+ )
+ const colorScheme = page.evaluate(() => {
+ return {
+ isDarkColorScheme: window.matchMedia('(prefers-color-scheme: dark)').matches
+ };
+ });
+ check(colorScheme, {
+ 'isDarkColorScheme': cs => cs.isDarkColorScheme
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_cookies.js b/examples/sync_cookies.js
new file mode 100644
index 000000000..34081ba25
--- /dev/null
+++ b/examples/sync_cookies.js
@@ -0,0 +1,127 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+};
+
+export default async function () {
+ const page = browser.newPage();
+ const context = page.context();
+
+ try {
+ // get cookies from the browser context
+ check(context.cookies().length, {
+ 'initial number of cookies should be zero': n => n === 0,
+ });
+
+ // add some cookies to the browser context
+ const unixTimeSinceEpoch = Math.round(new Date() / 1000);
+ const day = 60*60*24;
+ const dayAfter = unixTimeSinceEpoch+day;
+ const dayBefore = unixTimeSinceEpoch-day;
+ context.addCookies([
+ // this cookie expires at the end of the session
+ {
+ name: 'testcookie',
+ value: '1',
+ sameSite: 'Strict',
+ domain: '127.0.0.1',
+ path: '/',
+ httpOnly: true,
+ secure: true,
+ },
+ // this cookie expires in a day
+ {
+ name: 'testcookie2',
+ value: '2',
+ sameSite: 'Lax',
+ domain: '127.0.0.1',
+ path: '/',
+ expires: dayAfter,
+ },
+ // this cookie expires in the past, so it will be removed.
+ {
+ name: 'testcookie3',
+ value: '3',
+ sameSite: 'Lax',
+ domain: '127.0.0.1',
+ path: '/',
+ expires: dayBefore
+ }
+ ]);
+ let cookies = context.cookies();
+ check(cookies.length, {
+ 'number of cookies should be 2': n => n === 2,
+ });
+ check(cookies[0], {
+ 'cookie 1 name should be testcookie': c => c.name === 'testcookie',
+ 'cookie 1 value should be 1': c => c.value === '1',
+ 'cookie 1 should be session cookie': c => c.expires === -1,
+ 'cookie 1 should have domain': c => c.domain === '127.0.0.1',
+ 'cookie 1 should have path': c => c.path === '/',
+ 'cookie 1 should have sameSite': c => c.sameSite == 'Strict',
+ 'cookie 1 should be httpOnly': c => c.httpOnly === true,
+ 'cookie 1 should be secure': c => c.secure === true,
+ });
+ check(cookies[1], {
+ 'cookie 2 name should be testcookie2': c => c.name === 'testcookie2',
+ 'cookie 2 value should be 2': c => c.value === '2',
+ });
+
+ // let's add more cookies to filter by urls.
+ context.addCookies([
+ {
+ name: 'foo',
+ value: '42',
+ sameSite: 'Strict',
+ url: 'http://foo.com'
+ },
+ {
+ name: 'bar',
+ value: '43',
+ sameSite: 'Lax',
+ url: 'https://bar.com'
+ },
+ {
+ name: 'baz',
+ value: '44',
+ sameSite: 'Lax',
+ url: 'https://baz.com'
+ }
+ ]);
+ cookies = context.cookies('http://foo.com', 'https://baz.com');
+ check(cookies.length, {
+ 'number of filtered cookies should be 2': n => n === 2,
+ });
+ check(cookies[0], {
+ 'the first filtered cookie name should be foo': c => c.name === 'foo',
+ 'the first filtered cookie value should be 42': c => c.value === '42',
+ });
+ check(cookies[1], {
+ 'the second filtered cookie name should be baz': c => c.name === 'baz',
+ 'the second filtered cookie value should be 44': c => c.value === '44',
+ });
+
+ // clear cookies
+ context.clearCookies();
+ cookies = context.cookies();
+ check(cookies.length, {
+ 'number of cookies should be zero': n => n === 0,
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_device_emulation.js b/examples/sync_device_emulation.js
new file mode 100644
index 000000000..5759928ad
--- /dev/null
+++ b/examples/sync_device_emulation.js
@@ -0,0 +1,51 @@
+import { check, sleep } from 'k6';
+import { browser, devices } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const device = devices['iPhone X'];
+ // The spread operator is currently unsupported by k6's Babel, so use
+ // Object.assign instead to merge browser context and device options.
+ // See https://github.com/grafana/k6/issues/2296
+ const options = Object.assign({ locale: 'es-ES' }, device);
+ const context = browser.newContext(options);
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://k6.io/', { waitUntil: 'networkidle' });
+ const dimensions = page.evaluate(() => {
+ return {
+ width: document.documentElement.clientWidth,
+ height: document.documentElement.clientHeight,
+ deviceScaleFactor: window.devicePixelRatio
+ };
+ });
+
+ check(dimensions, {
+ 'width': d => d.width === device.viewport.width,
+ 'height': d => d.height === device.viewport.height,
+ 'scale': d => d.deviceScaleFactor === device.deviceScaleFactor,
+ });
+
+ if (!__ENV.K6_BROWSER_HEADLESS) {
+ sleep(10);
+ }
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_dispatch.js b/examples/sync_dispatch.js
new file mode 100644
index 000000000..5414eae8c
--- /dev/null
+++ b/examples/sync_dispatch.js
@@ -0,0 +1,36 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+
+ page.locator('a[href="/contacts.php"]')
+ .dispatchEvent("click");
+
+ check(page, {
+ header: (p) => p.locator("h3").textContent() == "Contact us",
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_elementstate.js b/examples/sync_elementstate.js
new file mode 100644
index 000000000..57d4f1c87
--- /dev/null
+++ b/examples/sync_elementstate.js
@@ -0,0 +1,47 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ // Inject page content
+ page.setContent(`
+
Hello world
+
+ Edit me
+
+
+
+
+ `);
+
+ // Check state
+ check(page, {
+ 'visible': p => p.$('.visible').isVisible(),
+ 'hidden': p => p.$('.hidden').isHidden(),
+ 'editable': p => p.$('.editable').isEditable(),
+ 'enabled': p => p.$('.enabled').isEnabled(),
+ 'disabled': p => p.$('.disabled').isDisabled(),
+ 'checked': p => p.$('.checked').isChecked(),
+ 'unchecked': p => p.$('.unchecked').isChecked() === false,
+ });
+
+ page.close();
+}
diff --git a/examples/sync_evaluate.js b/examples/sync_evaluate.js
new file mode 100644
index 000000000..ec0f0326c
--- /dev/null
+++ b/examples/sync_evaluate.js
@@ -0,0 +1,46 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto("https://test.k6.io/", { waitUntil: "load" });
+
+ // calling evaluate without arguments
+ let result = page.evaluate(() => {
+ return Promise.resolve(5 * 42);
+ });
+ check(result, {
+ "result should be 210": (result) => result == 210,
+ });
+
+ // calling evaluate with arguments
+ result = page.evaluate(([x, y]) => {
+ return Promise.resolve(x * y);
+ }, [5, 5]
+ );
+ check(result, {
+ "result should be 25": (result) => result == 25,
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_fillform.js b/examples/sync_fillform.js
new file mode 100644
index 000000000..dfa868a70
--- /dev/null
+++ b/examples/sync_fillform.js
@@ -0,0 +1,55 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ // Goto front page, find login link and click it
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('a[href="/my_messages.php"]').click(),
+ ]);
+ // Enter login credentials and login
+ page.locator('input[name="login"]').type('admin');
+ page.locator('input[name="password"]').type('123');
+ // We expect the form submission to trigger a navigation, so to prevent a
+ // race condition, setup a waiter concurrently while waiting for the click
+ // to resolve.
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('input[type="submit"]').click(),
+ ]);
+ check(page, {
+ 'header': p => p.locator('h2').textContent() == 'Welcome, admin!',
+ });
+
+ // Check whether we receive cookies from the logged site.
+ check(context.cookies(), {
+ 'session cookie is set': cookies => {
+ const sessionID = cookies.find(c => c.name == 'sid')
+ return typeof sessionID !== 'undefined'
+ }
+ })
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_getattribute.js b/examples/sync_getattribute.js
new file mode 100644
index 000000000..6a2bca532
--- /dev/null
+++ b/examples/sync_getattribute.js
@@ -0,0 +1,35 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://googlechromelabs.github.io/dark-mode-toggle/demo/', {
+ waitUntil: 'load',
+ });
+ let el = page.$('#dark-mode-toggle-3')
+ check(el, {
+ "GetAttribute('mode')": e => e.getAttribute('mode') == 'light',
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_grant_permission.js b/examples/sync_grant_permission.js
new file mode 100644
index 000000000..0df1a78db
--- /dev/null
+++ b/examples/sync_grant_permission.js
@@ -0,0 +1,35 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ // grant camera and microphone permissions to the
+ // new browser context.
+ const context = browser.newContext({
+ permissions: ["camera", "microphone"],
+ });
+
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/');
+ page.screenshot({ path: `example-chromium.png` });
+ context.clearPermissions();
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_hosts.js b/examples/sync_hosts.js
new file mode 100644
index 000000000..07a4c2607
--- /dev/null
+++ b/examples/sync_hosts.js
@@ -0,0 +1,33 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ hosts: { 'test.k6.io': '127.0.0.254' },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+};
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ const res = await page.goto('http://test.k6.io/', { waitUntil: 'load' });
+ check(res, {
+ 'null response': r => r === null,
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_keyboard.js b/examples/sync_keyboard.js
new file mode 100644
index 000000000..80da5168a
--- /dev/null
+++ b/examples/sync_keyboard.js
@@ -0,0 +1,32 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ }
+}
+
+export default async function () {
+ const page = browser.newPage();
+
+ await page.goto('https://test.k6.io/my_messages.php', { waitUntil: 'networkidle' });
+
+ const userInput = page.locator('input[name="login"]');
+ await userInput.click();
+ page.keyboard.type('admin');
+
+ const pwdInput = page.locator('input[name="password"]');
+ await pwdInput.click();
+ page.keyboard.type('123');
+
+ page.keyboard.press('Enter'); // submit
+
+ await page.close();
+}
diff --git a/examples/sync_locator.js b/examples/sync_locator.js
new file mode 100644
index 000000000..a7b189809
--- /dev/null
+++ b/examples/sync_locator.js
@@ -0,0 +1,74 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto("https://test.k6.io/flip_coin.php", {
+ waitUntil: "networkidle",
+ })
+
+ /*
+ In this example, we will use two locators, matching a
+ different betting button on the page. If you were to query
+ the buttons once and save them as below, you would see an
+ error after the initial navigation. Try it!
+
+ const heads = page.$("input[value='Bet on heads!']");
+ const tails = page.$("input[value='Bet on tails!']");
+
+ The Locator API allows you to get a fresh element handle each
+ time you use one of the locator methods. And, you can carry a
+ locator across frame navigations. Let's create two locators;
+ each locates a button on the page.
+ */
+ const heads = page.locator("input[value='Bet on heads!']");
+ const tails = page.locator("input[value='Bet on tails!']");
+
+ const currentBet = page.locator("//p[starts-with(text(),'Your bet: ')]");
+
+ // In the following Promise.all the tails locator clicks
+ // on the tails button by using the locator's selector.
+ // Since clicking on each button causes page navigation,
+ // waitForNavigation is needed -- this is because the page
+ // won't be ready until the navigation completes.
+ // Setting up the waitForNavigation first before the click
+ // is important to avoid race conditions.
+ await Promise.all([
+ page.waitForNavigation(),
+ tails.click(),
+ ]);
+ console.log(currentBet.innerText());
+ // the heads locator clicks on the heads button
+ // by using the locator's selector.
+ await Promise.all([
+ page.waitForNavigation(),
+ heads.click(),
+ ]);
+ console.log(currentBet.innerText());
+ await Promise.all([
+ page.waitForNavigation(),
+ tails.click(),
+ ]);
+ console.log(currentBet.innerText());
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_locator_pom.js b/examples/sync_locator_pom.js
new file mode 100644
index 000000000..ca5ccc9b6
--- /dev/null
+++ b/examples/sync_locator_pom.js
@@ -0,0 +1,77 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+/*
+Page Object Model is a well-known pattern to abstract a web page.
+
+The Locator API enables using the Page Object Model pattern to organize
+and simplify test code.
+
+Note: For comparison, you can see another example that does not use
+the Page Object Model pattern in locator.js.
+*/
+export class Bet {
+ constructor(page) {
+ this.page = page;
+ this.headsButton = page.locator("input[value='Bet on heads!']");
+ this.tailsButton = page.locator("input[value='Bet on tails!']");
+ this.currentBet = page.locator("//p[starts-with(text(),'Your bet: ')]");
+ }
+
+ goto() {
+ return this.page.goto("https://test.k6.io/flip_coin.php", { waitUntil: "networkidle" });
+ }
+
+ heads() {
+ return Promise.all([
+ this.page.waitForNavigation(),
+ this.headsButton.click(),
+ ]);
+ }
+
+ tails() {
+ return Promise.all([
+ this.page.waitForNavigation(),
+ this.tailsButton.click(),
+ ]);
+ }
+
+ current() {
+ return this.currentBet.innerText();
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ const bet = new Bet(page);
+ try {
+ await bet.goto()
+ await bet.tails();
+ console.log("Current bet:", bet.current());
+ await bet.heads();
+ console.log("Current bet:", bet.current());
+ await bet.tails();
+ console.log("Current bet:", bet.current());
+ await bet.heads();
+ console.log("Current bet:", bet.current());
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_mouse.js b/examples/sync_mouse.js
new file mode 100644
index 000000000..045e73fa9
--- /dev/null
+++ b/examples/sync_mouse.js
@@ -0,0 +1,27 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ }
+}
+
+export default async function () {
+ const page = browser.newPage();
+
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+
+ // Obtain ElementHandle for news link and navigate to it
+ // by clicking in the 'a' element's bounding box
+ const newsLinkBox = page.$('a[href="/news.php"]').boundingBox();
+ await page.mouse.click(newsLinkBox.x + newsLinkBox.width / 2, newsLinkBox.y);
+
+ await page.close();
+}
diff --git a/examples/sync_multiple-scenario.js b/examples/sync_multiple-scenario.js
new file mode 100644
index 000000000..ab48a9e50
--- /dev/null
+++ b/examples/sync_multiple-scenario.js
@@ -0,0 +1,53 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ messages: {
+ executor: 'constant-vus',
+ exec: 'messages',
+ vus: 2,
+ duration: '2s',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ news: {
+ executor: 'per-vu-iterations',
+ exec: 'news',
+ vus: 2,
+ iterations: 4,
+ maxDuration: '5s',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ browser_web_vital_fcp: ['max < 5000'],
+ checks: ["rate==1.0"]
+ }
+}
+
+export async function messages() {
+ const page = browser.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/my_messages.php', { waitUntil: 'networkidle' });
+ } finally {
+ page.close();
+ }
+}
+
+export async function news() {
+ const page = browser.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/news.php', { waitUntil: 'networkidle' });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_pageon.js b/examples/sync_pageon.js
new file mode 100644
index 000000000..77c2bdeff
--- /dev/null
+++ b/examples/sync_pageon.js
@@ -0,0 +1,39 @@
+import { browser } from 'k6/x/browser';
+import { check } from 'k6';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const page = browser.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/');
+
+ page.on('console', msg => {
+ check(msg, {
+ 'assertConsoleMessageType': msg => msg.type() == 'log',
+ 'assertConsoleMessageText': msg => msg.text() == 'this is a console.log message 42',
+ 'assertConsoleMessageArgs0': msg => msg.args()[0].jsonValue() == 'this is a console.log message',
+ 'assertConsoleMessageArgs1': msg => msg.args()[1].jsonValue() == 42,
+ });
+ });
+
+ page.evaluate(() => console.log('this is a console.log message', 42));
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_querying.js b/examples/sync_querying.js
new file mode 100644
index 000000000..96d7a0d8a
--- /dev/null
+++ b/examples/sync_querying.js
@@ -0,0 +1,35 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/');
+ check(page, {
+ 'Title with CSS selector':
+ p => p.$('header h1.title').textContent() == 'test.k6.io',
+ 'Title with XPath selector':
+ p => p.$(`//header//h1[@class="title"]`).textContent() == 'test.k6.io',
+ });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_screenshot.js b/examples/sync_screenshot.js
new file mode 100644
index 000000000..fb8b59407
--- /dev/null
+++ b/examples/sync_screenshot.js
@@ -0,0 +1,31 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/');
+ page.screenshot({ path: 'screenshot.png' });
+ // TODO: Assert this somehow. Upload as CI artifact or just an external `ls`?
+ // Maybe even do a fuzzy image comparison against a preset known good screenshot?
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_shadowdom.js b/examples/sync_shadowdom.js
new file mode 100644
index 000000000..0916a3706
--- /dev/null
+++ b/examples/sync_shadowdom.js
@@ -0,0 +1,36 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const page = browser.newPage();
+ page.setContent("hello!")
+ await page.evaluate(() => {
+ const shadowRoot = document.createElement('div');
+ shadowRoot.id = 'shadow-root';
+ shadowRoot.attachShadow({mode: 'open'});
+ shadowRoot.shadowRoot.innerHTML = 'Shadow DOM
';
+ document.body.appendChild(shadowRoot);
+ });
+ const shadowEl = page.locator("#find");
+ check(shadowEl, {
+ "shadow element exists": (e) => e !== null,
+ "shadow element text is correct": (e) => e.innerText() === "Shadow DOM",
+ });
+ page.close();
+}
diff --git a/examples/sync_throttle.js b/examples/sync_throttle.js
new file mode 100644
index 000000000..295589718
--- /dev/null
+++ b/examples/sync_throttle.js
@@ -0,0 +1,76 @@
+import { browser, networkProfiles } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ normal: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ exec: 'normal',
+ },
+ networkThrottled: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ exec: 'networkThrottled',
+ },
+ cpuThrottled: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ exec: 'cpuThrottled',
+ },
+ },
+ thresholds: {
+ 'browser_http_req_duration{scenario:normal}': ['p(99)<500'],
+ 'browser_http_req_duration{scenario:networkThrottled}': ['p(99)<3000'],
+ 'iteration_duration{scenario:normal}': ['p(99)<4000'],
+ 'iteration_duration{scenario:cpuThrottled}': ['p(99)<10000'],
+ },
+}
+
+export async function normal() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+ } finally {
+ page.close();
+ }
+}
+
+export async function networkThrottled() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ page.throttleNetwork(networkProfiles['Slow 3G']);
+
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+ } finally {
+ page.close();
+ }
+}
+
+export async function cpuThrottled() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ page.throttleCPU({ rate: 4 });
+
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+ } finally {
+ page.close();
+ }
+}
diff --git a/examples/sync_touchscreen.js b/examples/sync_touchscreen.js
new file mode 100644
index 000000000..3e2121760
--- /dev/null
+++ b/examples/sync_touchscreen.js
@@ -0,0 +1,32 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ }
+}
+
+export default async function () {
+ const page = browser.newPage();
+
+ await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
+
+ // Obtain ElementHandle for news link and navigate to it
+ // by tapping in the 'a' element's bounding box
+ const newsLinkBox = page.$('a[href="/news.php"]').boundingBox();
+ await page.touchscreen.tap(newsLinkBox.x + newsLinkBox.width / 2, newsLinkBox.y);
+
+ // Wait until the navigation is done before closing the page.
+ // Otherwise, there will be a race condition between the page closing
+ // and the navigation.
+ await page.waitForNavigation({ waitUntil: 'networkidle' });
+
+ page.close();
+}
diff --git a/examples/sync_waitForEvent.js b/examples/sync_waitForEvent.js
new file mode 100644
index 000000000..8cd3d2d1a
--- /dev/null
+++ b/examples/sync_waitForEvent.js
@@ -0,0 +1,39 @@
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ browser: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+}
+
+export default async function() {
+ const context = browser.newContext()
+
+ // We want to wait for two page creations before carrying on.
+ var counter = 0
+ const promise = context.waitForEvent("page", { predicate: page => {
+ if (++counter == 2) {
+ return true
+ }
+ return false
+ } })
+
+ // Now we create two pages.
+ const page = context.newPage()
+ const page2 = context.newPage()
+
+ // We await for the page creation events to be processed and the predicate
+ // to pass.
+ await promise
+ console.log('predicate passed')
+
+ page.close()
+ page2.close()
+};
diff --git a/examples/sync_waitforfunction.js b/examples/sync_waitforfunction.js
new file mode 100644
index 000000000..506d522c6
--- /dev/null
+++ b/examples/sync_waitforfunction.js
@@ -0,0 +1,41 @@
+import { check } from 'k6';
+import { browser } from 'k6/x/browser';
+
+export const options = {
+ scenarios: {
+ ui: {
+ executor: 'shared-iterations',
+ options: {
+ browser: {
+ type: 'chromium',
+ },
+ },
+ },
+ },
+ thresholds: {
+ checks: ["rate==1.0"]
+ }
+}
+
+export default async function() {
+ const context = browser.newContext();
+ const page = context.newPage();
+
+ try {
+ page.evaluate(() => {
+ setTimeout(() => {
+ const el = document.createElement('h1');
+ el.innerHTML = 'Hello';
+ document.body.appendChild(el);
+ }, 1000);
+ });
+
+ const ok = await page.waitForFunction("document.querySelector('h1')", {
+ polling: 'mutation',
+ timeout: 2000,
+ });
+ check(ok, { 'waitForFunction successfully resolved': ok.innerHTML() == 'Hello' });
+ } finally {
+ page.close();
+ }
+}