Context plugins

@nut-tree/playwright-bridge


Installation

npm i @nut-tree/playwright-bridge

Buy

@nut-tree/playwright-bridge is included in the Solo and Team plans, as well as the OCR and nl-matcher packages.


Description

@nut-tree/playwright-bridge is not a classical nut.js plugin like e.g. @nut-tree/nl-matcher. It is a context provider that allows you to use Playwright in your nut.js scripts.

By passing either a BrowserContext or Page object to the Playwright bridge, nut.js can interact with the browser controlled by Playwright. This allows you to switch the nut.js context from your whole desktop to just the browser viewport, making it easy to use the nut.js image search or OCR features in the context of your Playwright tests.


Usage

Simply require / import the package to wire up the provider using either usePage or useContext:

import {
    straightTo,
    centerOf,
    mouse,
    Button,
    screen
} from "@nut-tree/nut-js";
import "@nut-tree/nl-matcher";

import {chromium, selectors} from 'playwright';
import {useContext, usePage} from "@nut-tree/playwright-bridge";

const browser = await chromium.launch({headless: true});
const ctx = await browser.newContext();

// Pass the Playwright context to the useContext function of the Playwright bridge
// From here on we're no longer using the whole desktop, but just the browser viewport
// Every action we carry out will be in the context of the browser, image search, mouse movement, keyboard input etc.
useContext({context: ctx});

const firstPage = await ctx.newPage();
await firstPage.goto('https://www.google.com');

// Alternatively, you can pass a Playwright page object to the usePage function
// This will make the Playwright page the active context for nut.js
// This is useful if you want to interact with a specific page in your Playwright test
//usePage({page: firstPage});

// This will scroll the browser viewport, even in headless mode with other applications focused
await mouse.scrollDown(40);

// Same goes for image search and mouse movement.
// All of this happens in the context of your Playwright test, not on your desktop.
// So even in headless mode, image search is targeting only your browser viewport.
await mouse.move(
    straightTo(
        centerOf(
            screen.find(imageResource("cookies.png"))
        )
    )
);
await mouse.click(Button.LEFT);

await ctx.close();
await browser.close();

Configuration

The only additional piece of configuration that both useContext and usePage accept is the trackPageChanges property.

export type PlaywrightBridgePageOptions = {
    page: Page;
    trackPageChanges?: boolean;
}
export type PlaywrightBridgeContextConfigOptions = {
    context: BrowserContext;
    trackPageChanges?: boolean;
}

When set to true, the Playwright bridge will track changes to the page and context and update the nut.js context accordingly. This way you don't have to call usePage with a new page object every time you navigate to a new page in your Playwright test, like in this Playwright example.

Playwright locators

@nut-tree/playwright-bridge also provides a custom selector engine that allows you to locate elements on a Playwright page by their position. This is useful when you want to interact with elements on a page that you can't easily locate by their attributes.

To use this custom locator, it has to be registered BEFORE a page is created! See Playwright documentation for reference

Let's revisit the previous example and see how it would look like with the custom locator:

import {
    centerOf,
    screen
} from "@nut-tree/nut-js";
import "@nut-tree/nl-matcher";

import {chromium, selectors} from 'playwright';
import {useContext, locateByPosition} from "@nut-tree/playwright-bridge";

// Register the custom locator by name `atPosition`
await selectors.register('atPosition', locateByPosition);

const browser = await chromium.launch({headless: true});
const ctx = await browser.newContext();

useContext({context: ctx});

const firstPage = await ctx.newPage();
await firstPage.goto('https://www.google.com');
await mouse.scrollDown(40);

// Search for the image and retrieve its center
const imagePosition = await centerOf(screen.find(imageResource("cookies.png")));

// Instead of using the typical nut.js way of mouse.move(...), mouse.click(...), we can just use the custom locator and continue using the Playwright syntax for clicking, typing etc.
await firstPage.locator(`atPosition=${firstPostion}`).click();

await ctx.close();
await browser.close();

Usage example

import {chromium, selectors} from 'playwright';
import {locateByPosition, useContext} from "@nut-tree/playwright-bridge";
import {centerOf, ConsoleLogLevel, imageResource, keyboard, mouse, screen, useConsoleLogger} from "@nut-tree/nut-js";
import "@nut-tree/nl-matcher";
import {useBoltWindowFinder} from "@nut-tree/bolt";

await selectors.register('atPosition', locateByPosition);

useConsoleLogger({logLevel: ConsoleLogLevel.DEBUG});
useBoltWindowFinder();

const runHeadless = false;

const browser = await chromium.launch({headless: runHeadless});
const ctx = await browser.newContext();

if (runHeadless) {
    screen.config.resourceDirectory = './headless';
} else {
    screen.config.resourceDirectory = './non-headless';
}

keyboard.config.autoDelayMs = 10;
mouse.config.mouseSpeed = 3000;
screen.config.autoHighlight = true;

useContext({context: ctx});

const firstPage = await ctx.newPage();
await firstPage.goto('https://www.google.com');
await mouse.scrollDown(40);

const firstPostion = await centerOf(screen.find(imageResource("target.png"), {
    confidence: 0.9,
    providerData: {validateMatches: true}
}));
await firstPage.locator(`atPosition=${firstPostion}`).click();
const searchBox = await screen.find(imageResource('search.png'));
const searchBoxPosition = await centerOf(searchBox);
const box = await firstPage.locator('atPosition=' + searchBoxPosition);
await box.click();
await box.fill('nut.js');
await box.press('Enter');
const result = await screen.waitFor(imageResource('google.png'), 7000, 1000);
await firstPage.screenshot({
    path: 'result.png',
    clip: {x: result.left, y: result.top, width: result.width, height: result.height}
})

await ctx.close();
await browser.close();

Usage with @playwright/test

@nut-tree/playwright-bridge can be used in combination with @playwright/test to integrate nut.js features like image search or OCR into your Playwright tests. To do so in the most efficient way, nut.js provides a set of custom matchers for @playwright/test that allow you to use nut.js features as part of your Playwright tests.

To set up the custom matchers, you need to import playwrightMatchers from @nut-tree/playwright-bridge and pass it on to expect.extend(playwrightMatchers);.

import { expect, test } from "@playwright/test";
import { playwrightMatchers } from "@nut-tree/playwright-bridge";

// From here on, you can use the custom matchers provided by nut.js
expect.extend(playwrightMatchers);

Once set up, you can make use of nut.js matchers to write your test expectations.

Available matchers

toBeAt

toBeAt is a custom matcher that allows you to verify that your mouse is at a specific position on screen.

await expect(mouse).toBeAt(new Point(100, 100));
await expect(mouse).not.toBeAt(new Point(10, 10));

toBeIn

toBeIn is a custom matcher that allows you to verify that your mouse is within a specific region on screen.

await expect(mouse).toBeIn(new Region(10, 25, 70, 30));
await expect(mouse).not.toBeIn(new Region(0, 0, 10, 10));

toShow

toShow is a versatile custom matcher that allows you to verify that an image is shown on screen, a text is displayed on screen, or a specific color is present on screen.

You can pass any search query supported by find and it'll verify whether the queried item is present on screen.

await expect(screen).toShow(imageResource("image.png"));
await expect(screen).toShow(textLine("Hello, World!"));
await expect(screen).toShow(pixelWithColor(new RGBA(255, 0, 0, 255)));
await expect(screen).not.toShow(windowWithTitle(/Explorer/));

toWaitFor

toWaitFor is to toShow what screen.waitFor is to screen.find. It allows you to wait any search query supported by find to appear on screen within a specified timeout.

Its signature is similar to screen.waitFor, so you can specify both a timeout and an update interval.

await expect(screen).toWaitFor(imageResource("image.png"), 6000, 300);
await expect(screen).toWaitFor(textLine("Hello, World!"), 7000, 500, {providerData: {preprocessConfig: {binarize: false}}});
await expect(screen).toWaitFor(pixelWithColor(new RGBA(255, 0, 0, 255)));
await expect(screen).not.toWaitFor(windowWithTitle(/Explorer/));

The default values for timeoutMs and updateIntervalMs are 5000 and 500, respectively.

toHaveColor

toHaveColor allows you to verify that a specific color is present at a given position on screen.

const pointInQuestion = new Point(100, 100);
await expect(pointInQuestion).toHaveColor(pixelWithColor(new RGBA(255, 0, 0, 255)));
await expect(pointInQuestion).not.toHaveColor(pixelWithColor(new RGBA(255, 0, 255, 255)));

Full example

import "@nut-tree/nl-matcher";

import {
    Button,
    Key,
    centerOf,
    imageResource,
    keyboard,
    mouse,
    screen,
    sleep,
    textLine,
} from "@nut-tree/nut-js";
import { expect, test } from "@playwright/test";
import { playwrightMatchers, usePage } from "@nut-tree/playwright-bridge";

// From here on, you can use the custom matchers provided by nut.js
expect.extend(playwrightMatchers);

test("create new program", async ({ page }) => {
    // Pass the Playwright page object to the usePage function to make the Playwright page the active context for nut.js
    usePage({ page });

    const newButton = await screen.waitFor(
        imageResource("new_button.png"),
        7000,
        500
    );
    await mouse.setPosition(centerOf(newButton));
    await mouse.click(Button.LEFT);

    const newActionButton = await screen.waitFor(
        imageResource("new_action.png"),
        7000,
        500,
        { providerData: { validateMatches: true } }
    );
    await mouse.setPosition(centerOf(newActionButton));
    await mouse.click(Button.LEFT);
    await sleep(300);
    await keyboard.type("my_new_test");
    await keyboard.type(Key.Enter);
    
    // The following test expectations are using the custom matchers provided by nut.js
    await expect(screen).toWaitFor(
        textLine(/^my_new_test . Ready$/),
        10000,
        500,
        { confidence: 0.8 }
    );
    await expect(screen).not.toShow(imageResource("error.png"), {
        providerData: { validateMatches: true },
    });
});

Buy

@nut-tree/playwright-bridge is included in the Solo and Team plans, as well as the OCR and nl-matcher packages.

Previous
@nut-tree/element-inspector