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(); + } +}