browser

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 25, 2022 License: AGPL-3.0 Imports: 7 Imported by: 0

README

xk6-browser

Browser automation and end-to-end web testing for k6

An extension for k6 adding browser-level APIs with rough Playwright compatibility.

Github release Build status Go Report Card
Slack channel

Download · Install · Documentation · Community Forum


---

xk6-browser is a k6 extension adding support for automation of browsers via the Chrome Devtools Protocol (CDP).

Special acknowledgment to the authors of Playwright and Puppeteer for their trailblazing work in this area. This project is heavily influenced and in some regards based on the code of those projects.

Goals

  • Bring browser automation to the k6 testing platform while supporting core k6 features like VU executors, scenarios, metrics, checks, thresholds, logging, DNS remapping, IP blocklists, etc.
  • Test stability as the top priority by supporting non-flaky selectors combined with auto-waiting for actions just like Playwright.
  • Aim for rough API compatibility with Playwright. The reason for this is two-fold; for one we don't want users to have to learn a completely new API just to use xk6-browser, and secondly, it opens up for using the Playwright RPC server as an optional backend for xk6-browser should we decide to support that in the future.
  • Support for Chromium compatible browsers first, and eventually Firefox and WebKit-based browsers.

FAQ

  • Is this production ready? No, not yet. We're focused on making the extension stable and reliable, as that's our top priority, before adding more features.

  • Is this extension supported in k6 Cloud? No, not yet. Once the codebase is deemed production ready we'll add support for browser-based testing in k6 Cloud.

  • It doesn't work with my Chromium/Chrome version, why? CDP evolves and there are differences between different versions of Chromium, sometimes quite subtle. The codebase is continuously tested with the two latest major releases of Google Chrome.

  • Are Firefox or WebKit-based browsers supported? Not yet. There are differences in CDP coverage between Chromium, Firefox, and WebKit-based browsers. xk6-browser is initially only targetting Chromium-based browsers.

  • Are all features of Playwright supported? No. Playwright's API is pretty big and some of the functionality only makes sense if they're implemented as async operations: event listening, request interception, waiting for events, etc. As k6 doesn't have a VU event-loop yet, the xk6-browser API is synchronous right now and thus lacks some of the functionality that requires asynchronicity.

Install

Pre-built binaries

The easiest way to install xk6-browser is to grab a pre-built binary from the GitHub Releases page. Once you download and unpack the release, you can optionally copy the xk6-browser binary it contains somewhere in your PATH, so you are able to run xk6-browser from any location on your system.

Note that you cannot use the plain k6 binary released by the k6 project and must run any scripts that import k6/x/browser with this separate binary.

Build from source

To build a k6 binary with this extension, first ensure you have the prerequisites:

Then:

  1. Install xk6:
go install go.k6.io/xk6/cmd/xk6@latest
  1. Build the binary:
xk6 build --output xk6-browser --with github.com/grafana/xk6-browser

This will create a xk6-browser binary file in the current working directory. This file can be used exactly the same as the main k6 binary, with the addition of being able to run xk6-browser scripts.

  1. Run scripts that import k6/x/browser with the new xk6-browser binary. On Linux and macOS make sure this is done by referencing the file in the current directory:

    ./xk6-browser run <script>
    

    Note: You can place it somewhere in your PATH so that it can be run from anywhere on your system.

Examples

Launch options
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium', {
        args: [],                   // Extra commandline arguments to include when launching browser process
        debug: true,                // Log all CDP messages to k6 logging subsystem
        devtools: true,             // Open up developer tools in the browser by default
        env: {},                    // Environment variables to set before launching browser process
        executablePath: null,       // Override search for browser executable in favor of specified absolute path
        headless: false,            // Show browser UI or not
        ignoreDefaultArgs: [],      // Ignore any of the default arguments included when launching browser process
        proxy: {},                  // Specify to set browser's proxy config
        slowMo: '500ms',            // Slow down input actions and navigations by specified time
        timeout: '30s',             // Default timeout to use for various actions and navigations
    });
    browser.close();
}
New browser context options
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium');
    const context = browser.newContext({
        acceptDownloads: false,             // Whether to accept downloading of files by default
        bypassCSP: false,                   // Whether to bypass content-security-policy rules
        colorScheme: 'light',               // Preferred color scheme of browser ('light', 'dark' or 'no-preference')
        deviceScaleFactor: 1.0,             // Device scaling factor
        extraHTTPHeaders: {name: "value"},  // HTTP headers to always include in HTTP requests
        geolocation: {latitude: 0.0, longitude: 0.0},       // Geolocation to use
        hasTouch: false,                    // Simulate device with touch or not
        httpCredentials: {username: null, password: null},  // Credentials to use if encountering HTTP authentication
        ignoreHTTPSErrors: false,           // Ignore HTTPS certificate issues
        isMobile: false,                    // Simulate mobile device or not
        javaScriptEnabled: true,            // Should JavaScript be enabled or not
        locale: 'en-US',                    // The locale to set
        offline: false,                     // Whether to put browser in offline mode or not
        permissions: ['midi'],              // Permisions to grant by default
        reducedMotion: 'no-preference',     // Indicate to browser whether it should try to reduce motion/animations
        screen: {width: 800, height: 600},  // Set default screen size
        timezoneID: '',                     // Set default timezone to use
        userAgent: '',                      // Set default user-agent string to use
        viewport: {width: 800, height: 600},// Set default viewport to use
    });
    browser.close();
}
Page screenshot
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium', { headless: false });
    const context = browser.newContext();
    const page = context.newPage();
    page.goto('http://whatsmyuseragent.org/');
    page.screenshot({ path: `example-chromium.png` });
    page.close();
    browser.close();
}
Query DOM for element using CSS, XPath or Text based selectors
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium', { headless: false });
    const context = browser.newContext();
    const page = context.newPage();
    page.goto('http://whatsmyuseragent.org/');

    // Find element using CSS selector
    const ip = page.$('.ip-address p').textContent();
    console.log("CSS selector: ", ip);

    // Find element using XPath expression
    ip = page.$("//div[@class='ip-address']/p").textContent();
    console.log("Xpath expression: ", ip);

    // Find element using Text search (TODO: support coming soon!)
    //ip = page.$("My IP Address").textContent();
    //console.log("Text search: ", ip);

    page.close();
    browser.close();
}
Evaluate JS in browser
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium', { headless: false });
    const context = browser.newContext();
    const page = context.newPage();
    page.goto('http://whatsmyuseragent.org/', { waitUntil: 'load' });
    const dimensions = page.evaluate(() => {
        return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight,
            deviceScaleFactor: window.devicePixelRatio
        };
    });
    console.log(JSON.stringify(dimensions));
    page.close();
    browser.close();
}
Set preferred color scheme of browser
import launcher from "k6/x/browser";
import { sleep } from "k6";

export default function() {
    const browser = launcher.launch('chromium', {
        headless: false
    });
    const context = browser.newContext({
        colorScheme: 'dark', // Valid values are "light", "dark" or "no-preference"
    });
    const page = context.newPage();
    page.goto('http://whatsmyuseragent.org/');

    sleep(5);

    page.close();
    browser.close();
}
Fill out a form
import launcher from "k6/x/browser";

export default function() {
    const browser = launcher.launch('chromium', {
        headless: false,
        slowMo: '500ms' // slow down by 500ms
    });
    const context = browser.newContext();
    const page = context.newPage();

    // Goto front page, find login link and click it
    page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
    const elem = page.$('a[href="/my_messages.php"]');
    elem.click();

    // Enter login credentials and login
    page.$('input[name="login"]').type('admin');
    page.$('input[name="password"]').type('123');
    page.$('input[type="submit"]').click();

    // Wait for next page to load
    page.waitForLoadState('networkdidle');

    page.close();
    browser.close();
}
Check element state
import launcher from "k6/x/browser";
import { check } from "k6";

export default function() {
    const browser = launcher.launch('chromium', {
        headless: false
    });
    const context = browser.newContext();
    const page = context.newPage();

    // Inject page content
    page.setContent(`
        <div class="visible">Hello world</div>
        <div style="display:none" class="hidden"></div>
        <div class="editable" editable>Edit me</div>
        <input type="checkbox" enabled class="enabled">
        <input type="checkbox" disabled class="disabled">
        <input type="checkbox" checked class="checked">
        <input type="checkbox" class="unchecked">
    `);

    // 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();
    browser.close();
}

Status

Currently only Chromium is supported, and the Playwright API coverage is as follows:

Class Support Missing APIs
Accessibility snapshot()
Browser on() (dependent on event-loop support in k6), startTracing(), stopTracing()
BrowserContext addCookies(), backgroundPages(), cookies(), exposeBinding(), exposeFunction(), newCDPSession(), on() (dependent on event-loop support in k6), route() (dependent on event-loop support in k6), serviceWorkers(), storageState(), unroute() (dependent on event-loop support in k6), waitForEvent() (dependent on event-loop support in k6), tracing
BrowserServer All
BrowserType connect(), connectOverCDP(), launchPersistentContext(), launchServer()
CDPSession All
ConsoleMessage All
Coverage All
Dialog All
Download All
ElementHandle $eval(), $$eval(), setInputFiles()
FetchRequest All
FetchResponse All
FileChooser All
Frame $eval(), $$eval(), addScriptTag(), addStyleTag(), dragAndDrop(), locator(), setInputFiles()
JSHandle -
Keyboard -
Locator All
Logger All
Mouse -
Page $eval(), $$eval(), addInitScript(), addScriptTag(), addStyleTag(), dragAndDrop(), exposeBinding(), exposeFunction(), frame(), goBack(), goForward(), locator(), on() (dependent on event-loop support in k6), pause(), pdf(), route() (dependent on event-loop support in k6), unroute() (dependent on event-loop support in k6), video(), waitForEvent() (dependent on event-loop support in k6), waitForResponse() (dependent on event-loop support in k6), waitForURL() (dependent on event-loop support in k6), workers()
Request failure() (dependent on event-loop support in k6), postDataJSON(), redirectFrom(), redirectTo()
Response finished() (dependent on event-loop support in k6)
Route All
Selectors All
Touchscreen -
Tracing All
Video All
WebSocket All
Worker All

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type JSModule

type JSModule struct {
	Devices map[string]common.Device
	Version string
	// contains filtered or unexported fields
}

JSModule is the entrypoint into the browser JS module.

func (*JSModule) Launch

func (m *JSModule) Launch(browserName string, opts goja.Value) api.Browser

type ModuleInstance

type ModuleInstance struct {
	// contains filtered or unexported fields
}

ModuleInstance represents an instance of the JS module.

func (*ModuleInstance) Exports added in v0.1.1

func (mi *ModuleInstance) Exports() k6modules.Exports

Exports returns the exports of the JS module so that it can be used in test scripts.

type RootModule

type RootModule struct{}

RootModule is the global module instance that will create module instances for each VU.

func New

func New() *RootModule

New returns a pointer to a new RootModule instance.

func (*RootModule) NewModuleInstance

func (*RootModule) NewModuleInstance(vu k6modules.VU) k6modules.Instance

NewModuleInstance implements the k6modules.Module interface to return a new instance for each VU.

Directories

Path Synopsis
js
ws

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL