Skip to content

TypeScript test runner (Playwright)

An automated browser test runner with results you can access via API.

Create it locally

$scrapely create my-actor --template ts-playwright-test-runner

Main file

.TS
src/main.ts
import { execSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';

import { Actor, log } from 'scrapely';

import { collectAttachmentPaths, transformToTabular } from './transform.js';

function ensureFolder(pathname: string) {
    if (!fs.existsSync(pathname)) {
        fs.mkdirSync(pathname, { recursive: true });
    }
}

function getConfigPath() {
    return `${import.meta.dirname}/../playwright.config.ts`;
}

function getResultDir() {
    return `${import.meta.dirname}/../playwright-report`;
}

const getConfig = (options: {
    screen: { width: number; height: number };
    headful: boolean;
    timeout: number;
    locale: string;
    darkMode: boolean;
    ignoreHTTPSErrors: boolean;
    video: string;
}) => {
    const { screen, headful, timeout, ignoreHTTPSErrors, darkMode, locale, video } = options;

    return `
// Watch out! This file gets regenerated on every run of the actor.
// Any changes you make will be lost.

// Tweak your configuration through the Actor's input through the Scrapely Console or directly in the \`input.json\` file.
import { defineConfig } from '@playwright/test';
export default defineConfig({
    timeout: ${timeout},
    use: {
        headless: ${!headful},
        viewport: { width: ${screen.width}, height: ${screen.height} },
        ignoreHTTPSErrors: ${ignoreHTTPSErrors},
        colorScheme: '${darkMode ? 'dark' : 'light'}',
        locale: '${locale}',
        video: '${video}',
        launchOptions: {
            args: [
                '--disable-gpu', // Mitigates the "crashing GPU process" issue in Docker containers
            ]
        },
    },
    reporter: [
        ['html', { outputFolder: '${getResultDir()}', open: 'never' }],
        ['json', { outputFile: '${getResultDir()}/test-results.json' }]
    ],
});`;
};
function runTests() {
    try {
        execSync(`npx playwright test --config=${getConfigPath()}`, {
            cwd: import.meta.dirname,
            encoding: 'utf8',
            stdio: 'inherit',
        });
    } catch {
        // suppress error, the report will be generated anyway
    }
}

function updateConfig(args: {
    screenWidth?: number;
    screenHeight?: number;
    headful?: boolean;
    timeout?: number;
    darkMode?: boolean;
    locale?: string;
    ignoreHTTPSErrors?: boolean;
    video?: string;
}) {
    const {
        screenWidth = 1280,
        screenHeight = 720,
        headful = false,
        timeout = 60,
        darkMode = false,
        locale = 'en-US',
        ignoreHTTPSErrors = true,
        video = 'off',
    } = args;

    const config = getConfig({
        screen: { width: screenWidth, height: screenHeight },
        headful,
        timeout: timeout * 1000,
        locale,
        darkMode,
        ignoreHTTPSErrors,
        video,
    });
    fs.writeFileSync(getConfigPath(), config, { encoding: 'utf-8' });
}

await Actor.init();
const input = (await Actor.getInput<Parameters<typeof updateConfig>[0]>()) ?? {};

ensureFolder(getResultDir());
updateConfig(input);

runTests();

const kvs = await Actor.openKeyValueStore();
await kvs.setValue('report', fs.readFileSync(path.join(getResultDir(), 'index.html'), { encoding: 'utf-8' }), {
    contentType: 'text/html',
});
const jsonReport = JSON.parse(fs.readFileSync(path.join(getResultDir(), 'test-results.json'), { encoding: 'utf-8' }));
const attachmentPaths = collectAttachmentPaths(jsonReport);

const attachmentLinks = await Promise.all(
    attachmentPaths.map(async (x) => {
        const attachment = fs.readFileSync(x.path);
        await kvs.setValue(x.key, attachment, { contentType: x.type ?? 'application/octet' });
        return { ...x, url: kvs.getPublicUrl(x.key) };
    }),
);

await Actor.pushData(transformToTabular(jsonReport, attachmentLinks));

const reportURL = kvs.getPublicUrl('report');
log.info('The test run has finished! The report is available in the Output tab or at the link below:');
log.info(reportURL);

await Actor.exit();

TypeScript test runner (Playwright)

An automated browser test runner with results you can access via API.

Included features

  • Scrapely SDK — Actor lifecycle, input, storage and proxies
  • Playwright — headless browser automation
  • Input schema — define and validate the Actor's input (.actor/input_schema.json)
  • Dataset schema — structured results with a preview view (.actor/dataset_schema.json)

Getting started locally

npm install
npm run start      # run the Actor
npm run build      # compile to dist/

Deploying to Scrapely

Create an Actor from this template in the Scrapely Console (Develop new → template → Git). Scrapely creates a repository in your account, pushes this code, and builds the Actor. Every push to your repository triggers a new build.

G2
4.7/5
Excellent
Trustpilot

The internet is full of data.
Start owning yours.

Scrape.ly gives you the infrastructure, the tooling, and the team to extract, process, and operationalize web data at any scale. Setup takes minutes. The advantage lasts forever.

No credit card. No setup calls.