Files
2026-07-13 12:25:07 +08:00

257 lines
9.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃ This file is part of the Perspective library, distributed under the terms ┃
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
import { defineConfig, devices } from "@playwright/experimental-ct-react";
import path from "path";
import * as dotenv from "dotenv";
import { createRequire } from "node:module";
import url from "node:url";
import { execSync } from "child_process";
// @ts-ignore -- sh_perspective.mjs is plain JS without type declarations
import { get_scope } from "../scripts/sh_perspective.mjs";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config({ path: "./.perspectiverc", quiet: true });
Error.stackTraceLimit = Infinity;
// TODO Don't hardcode this. AFAICT this can only be accomplished by choosing
// the port before calling the playwright CLI, via env var.
const TEST_SERVER_PORT = 6598;
const RUN_JUPYTERLAB = !!process.env.PSP_JUPYTERLAB_TESTS;
// TODO use this from core
const package_venn = (get_scope() as string[]).reduce(
(acc: { include: string[]; exclude: string[] }, x: string) => {
if (x.startsWith("!")) {
acc.exclude.push(x);
} else {
acc.include.push(x);
}
return acc;
},
{ include: [] as string[], exclude: [] as string[] },
);
let PACKAGE: string[] = [];
if (package_venn.include.length === 0) {
PACKAGE = (
JSON.parse(execSync(`pnpm m ls --json --depth=-1`).toString()) as {
name?: string;
}[]
)
.filter((x): x is { name: string } => x.name !== undefined)
.map((x) => x.name.replace("@perspective-dev/", ""))
.filter((x: string) => package_venn.exclude.indexOf(`!${x}`) === -1);
} else {
PACKAGE = package_venn.include.filter(
(x: string) => package_venn.exclude.indexOf(`!${x}`) === -1,
);
}
const DEVICE_OPTIONS = {
"Desktop Chrome": {
// Lock DPR to 1 so WebGL viewport / canvas backing sizes are
// identical across host machines (matters for pixel snapshots).
// This also overrides the `devices["Desktop Chrome"]` default of 2.
deviceScaleFactor: 1,
launchOptions: {
args: [
// "--disable-accelerated-2d-canvas",
// "--disable-gpu",
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--font-render-hinting=none",
'--proxy-server="direct://"',
"--proxy-bypass-list=*",
"--js-flags=--expose-gc",
"--enable-precise-memory-info",
// Use the software WebGL backend so GPU/driver differences
// between machines don't leak into snapshot pixels. The
// 2× perf hit is under budget for the test suite.
"--use-gl=swiftshader",
"--use-angle=swiftshader",
"--force-color-profile=srgb",
"--disable-lcd-text",
],
},
},
};
const BROWSER_PACKAGES = [
{
packageName: "viewer",
testDir: "rust/perspective-viewer/test/js",
},
{
packageName: "react",
testDir: "packages/react/test/js",
},
{
packageName: "viewer-datagrid",
testDir: "packages/viewer-datagrid/test/js",
},
{
packageName: "viewer-charts",
testDir: "packages/viewer-charts/test/ts",
},
{
packageName: "jupyterlab",
testDir: "packages/jupyterlab/test/js",
},
{
packageName: "workspace",
testDir: "packages/workspace/test/js",
},
{
packageName: "docs",
testDir: "docs/test/js",
},
];
const NODE_PACKAGES = [
{
packageName: "client",
testDir: "rust/perspective-js/test/js",
},
];
const BROWSER_AND_PYTHON_PACKAGES = [
{
packageName: "python-perspective-jupyterlab",
testDir: "packages/jupyterlab/test/jupyter",
},
];
type ProjectConfig = NonNullable<
Parameters<typeof defineConfig>[0]["projects"]
>[number];
let PROJECTS = (() => {
const acc: ProjectConfig[] = [];
if (RUN_JUPYTERLAB) {
for (const pkg of BROWSER_AND_PYTHON_PACKAGES) {
for (const device of Object.keys(
DEVICE_OPTIONS,
) as (keyof typeof DEVICE_OPTIONS)[]) {
acc.push({
name: `${pkg.packageName}-${device
.toLowerCase()
.replace(" ", "-")}`,
testDir: path.join(__dirname, "../../", pkg.testDir),
use: {
...devices[device],
// baseURL: `http://localhost:${TEST_SERVER_PORT}`,
timezoneId: "UTC",
launchOptions: {
args: ["--js-flags=--expose-gc"],
},
},
});
}
}
} else {
for (const pkg of NODE_PACKAGES) {
if (PACKAGE == undefined || PACKAGE.includes(pkg.packageName)) {
acc.push({
name: `${pkg.packageName}-node`,
testDir: path.join(__dirname, "../../", pkg.testDir),
});
}
}
for (const pkg of BROWSER_PACKAGES) {
if (PACKAGE == undefined || PACKAGE.includes(pkg.packageName)) {
for (const device of Object.keys(
DEVICE_OPTIONS,
) as (keyof typeof DEVICE_OPTIONS)[]) {
acc.push({
name: `${pkg.packageName}-${device
.toLowerCase()
.replace(" ", "-")}`,
testDir: path.join(__dirname, "../../", pkg.testDir),
use: {
...devices[device],
...DEVICE_OPTIONS[device],
baseURL: `http://localhost:${TEST_SERVER_PORT}`,
timezoneId: "UTC",
},
});
}
}
}
}
return acc;
})();
const __require = createRequire(import.meta.url);
const GLOBAL_SETUP_PATH = __require.resolve(
"@perspective-dev/jupyterlab/test/config/jupyter/globalSetup.ts",
);
const GLOBAL_TEARDOWN_PATH = __require.resolve(
"@perspective-dev/jupyterlab/test/config/jupyter/globalTeardown.ts",
);
// See https://playwright.dev/docs/test-configuration.
export default defineConfig({
timeout: 30_000,
expect: {
timeout: 30_000,
},
repeatEach: process.env.PSP_SATURATE
? parseInt(process.env.PSP_SATURATE)
: 0,
forbidOnly: !!process.env.CI,
workers: process.env.PSP_DEBUG ? 1 : "50%",
retries: 0,
quiet: !process.env.PSP_DEBUG,
reporter: process.env.CI ? [["github"], ["html"]] : [["dot"]],
projects: PROJECTS,
outputDir: "dist/results",
use: {
launchOptions: {
slowMo: 500, // 500 milliseconds delay
},
headless: !process.env.PSP_HEADED,
ctPort: 3100,
viewport: { width: 1280, height: 720 },
actionTimeout: 0,
// trace: "retain-on-failure",
// screenshot: "only-on-failure",
// video: "retain-on-failure",
},
updateSnapshots: "none",
globalSetup: RUN_JUPYTERLAB
? GLOBAL_SETUP_PATH
: path.join(__dirname, "src/js/global_startup.ts"),
globalTeardown: RUN_JUPYTERLAB
? GLOBAL_TEARDOWN_PATH
: path.join(__dirname, "src/js/global_teardown.ts"),
snapshotPathTemplate:
"dist/snapshots/{projectName}/{testFilePath}/{arg}{ext}",
webServer: {
command: "tsx src/js/start_test_server.ts",
port: TEST_SERVER_PORT,
reuseExistingServer: true,
stdout: "pipe",
stderr: "pipe",
},
});