chore: import upstream snapshot with attribution
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
CI / compile_and_lint (push) Failing after 0s
CI / docker_build (linux/amd64, -linux-amd64-duckdb, duckdb) (push) Failing after 0s
CI / docker_build (linux/arm64, -linux-arm64, minimal) (push) Failing after 2s
CI / docker_build (linux/arm64, -linux-arm64-duckdb, duckdb) (push) Failing after 1s
CI / docker_build (linux/amd64, minimal) (push) Failing after 1s
CI / test (, sqlite, sqlite::memory:) (push) Has been skipped
CI / test (mssql, mssql, mssql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (mysql, mysql, mysql://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / test (oracle, oracle, Driver=Oracle 21 ODBC driver;Dbq=//127.0.0.1:1521/FREEPDB1;Uid=root;Pwd=Password123!) (push) Has been skipped
CI / test (postgres, odbc, Driver=PostgreSQL Unicode;Server=127.0.0.1;Port=5432;Database=sqlpage;UID=root;PWD=Password123!, true) (push) Has been skipped
CI / test (postgres, postgres, postgres://root:Password123!@127.0.0.1/sqlpage) (push) Has been skipped
CI / playwright (push) Has been skipped
CI / docker_build (linux/arm/v7, -linux-arm-v7, minimal) (push) Failing after 0s
CI / hurl_examples (push) Failing after 8s
deploy website / deploy_official_site (push) Failing after 1s
CI / hurl (${{ matrix.example }}) (push) Has been skipped
CI / docker_push (duckdb) (push) Has been cancelled
CI / docker_push (minimal) (push) Has been cancelled
CI / windows_test (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
@@ -0,0 +1,386 @@
|
||||
import { expect, type Page, test } from "@playwright/test";
|
||||
|
||||
const BASE = "http://localhost:8080/";
|
||||
|
||||
test("Open documentation", async ({ page }) => {
|
||||
await page.goto(BASE);
|
||||
|
||||
// Expect a title "to contain" a substring.
|
||||
await expect(page).toHaveTitle(/SQLPage.*/);
|
||||
|
||||
// open the submenu
|
||||
await page.getByText("Documentation", { exact: true }).first().click();
|
||||
const components = ["form", "map", "chart", "button"];
|
||||
for (const component of components) {
|
||||
await expect(
|
||||
page.getByRole("link", { name: component }).first(),
|
||||
).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test("chart", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=chart#component`);
|
||||
await expect(page.getByText("Loading...")).not.toBeVisible();
|
||||
await expect(page.locator(".apexcharts-canvas").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("chart supports hiding legend", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=chart#component`);
|
||||
|
||||
const expensesChart = page.locator(".card", {
|
||||
has: page.getByRole("heading", { name: "Expenses" }),
|
||||
});
|
||||
|
||||
await expect(expensesChart.locator(".apexcharts-canvas")).toBeVisible();
|
||||
await expect(expensesChart.locator(".apexcharts-legend")).toBeHidden();
|
||||
});
|
||||
|
||||
test("map", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=map#component`);
|
||||
await expect(page.getByText("Loading...")).not.toBeVisible();
|
||||
await expect(page.locator(".leaflet-marker-icon").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("form example", async ({ page }) => {
|
||||
await page.goto(`${BASE}/examples/multistep-form`);
|
||||
// Single selection matching the value or label
|
||||
await page.getByLabel("From").selectOption("Paris");
|
||||
await page.getByText("Next").click();
|
||||
await page.getByLabel(/\bTo\b/).selectOption("Mexico");
|
||||
await page.getByText("Next").click();
|
||||
await page.getByLabel("Number of Adults").fill("1");
|
||||
await page.getByText("Next").click();
|
||||
await page.getByLabel("Passenger 1 (adult)").fill("John Doe");
|
||||
await page.getByText("Book the flight").click();
|
||||
await expect(page.getByText("John Doe").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("File upload", async ({ page }) => {
|
||||
await page.goto(`${BASE}/your-first-sql-website`);
|
||||
await page.getByRole("button", { name: "Examples", exact: true }).click();
|
||||
await page.getByText("File uploads").click();
|
||||
const my_svg = '<svg><text y="20">Hello World</text></svg>';
|
||||
// @ts-ignore
|
||||
const buffer = Buffer.from(my_svg);
|
||||
await page.getByLabel("Picture").setInputFiles({
|
||||
name: "small.svg",
|
||||
mimeType: "image/svg+xml",
|
||||
buffer,
|
||||
});
|
||||
await page.getByRole("button", { name: "Upload picture" }).click();
|
||||
await expect(
|
||||
page.locator("img[src^=data]").first().getAttribute("src"),
|
||||
).resolves.toBe(`data:image/svg+xml;base64,${buffer.toString("base64")}`);
|
||||
});
|
||||
|
||||
test("Authentication example", async ({ page }) => {
|
||||
await page.goto(`${BASE}/examples/authentication/login.sql`);
|
||||
await expect(page.locator("h1", { hasText: "Authentication" })).toBeVisible();
|
||||
|
||||
const usernameInput = page.getByLabel("Username");
|
||||
const passwordInput = page.getByLabel("Password");
|
||||
const loginButton = page.getByRole("button", { name: "Log in" });
|
||||
|
||||
await expect(usernameInput).toBeVisible();
|
||||
await expect(passwordInput).toBeVisible();
|
||||
await expect(loginButton).toBeVisible();
|
||||
|
||||
await usernameInput.fill("admin");
|
||||
await passwordInput.fill("admin");
|
||||
await loginButton.click();
|
||||
|
||||
await expect(page.getByText("You are logged in as admin")).toBeVisible();
|
||||
});
|
||||
|
||||
test("table filtering", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".card", {
|
||||
has: page.getByRole("cell", { name: "Chart", exact: true }),
|
||||
});
|
||||
|
||||
const searchInput = tableSection.getByPlaceholder("Search…");
|
||||
await searchInput.fill("chart");
|
||||
const chartCell = tableSection.getByRole("cell", { name: "Chart" });
|
||||
await expect(chartCell).toBeVisible();
|
||||
await expect(chartCell).toHaveClass(/\b_col_name\b/);
|
||||
await expect(chartCell).toHaveCSS("vertical-align", "middle");
|
||||
await expect(
|
||||
tableSection.getByRole("cell", { name: "Table" }),
|
||||
).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("table sorting", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".table-responsive", {
|
||||
has: page.getByRole("cell", { name: "31456" }),
|
||||
});
|
||||
|
||||
// Test numeric sorting on id column
|
||||
await tableSection.getByRole("button", { name: "id" }).click();
|
||||
let ids = await tableSection.locator("td.id").allInnerTexts();
|
||||
let numericIds = ids.map((id) => Number.parseInt(id));
|
||||
const sortedIds = [...numericIds].sort((a, b) => a - b);
|
||||
expect(numericIds).toEqual(sortedIds);
|
||||
|
||||
// Test reverse sorting
|
||||
await tableSection.getByRole("button", { name: "id" }).click();
|
||||
ids = await tableSection.locator("td.id").allInnerTexts();
|
||||
numericIds = ids.map((id) => Number.parseInt(id));
|
||||
const reverseSortedIds = [...numericIds].sort((a, b) => b - a);
|
||||
expect(numericIds).toEqual(reverseSortedIds);
|
||||
|
||||
// Test amount in stock column sorting
|
||||
await tableSection.getByRole("button", { name: "Amount in stock" }).click();
|
||||
const amounts = await tableSection.locator("td.Amount").allInnerTexts();
|
||||
const numericAmounts = amounts.map((amount) =>
|
||||
Number.parseInt(amount.replace(/[^0-9]/g, "")),
|
||||
);
|
||||
const sortedAmounts = [...numericAmounts].sort((a, b) => a - b);
|
||||
expect(numericAmounts).toEqual(sortedAmounts);
|
||||
});
|
||||
|
||||
async function checkNoConsoleErrors(page: Page, component: string) {
|
||||
const errors: string[] = [];
|
||||
page.on("console", (msg) => {
|
||||
if (msg.type() === "error") {
|
||||
errors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto(`${BASE}/documentation.sql?component=${component}`);
|
||||
await page.waitForLoadState();
|
||||
|
||||
expect(errors).toHaveLength(0);
|
||||
}
|
||||
|
||||
test("no console errors on table page", async ({ page }) => {
|
||||
await checkNoConsoleErrors(page, "table");
|
||||
});
|
||||
|
||||
test("no console errors on chart page", async ({ page }) => {
|
||||
await checkNoConsoleErrors(page, "chart");
|
||||
});
|
||||
|
||||
test("no console errors on map page", async ({ page }) => {
|
||||
await checkNoConsoleErrors(page, "map");
|
||||
});
|
||||
|
||||
test("no console errors on card page", async ({ page }) => {
|
||||
await checkNoConsoleErrors(page, "card");
|
||||
});
|
||||
|
||||
test("CSP issues unique nonces per request", async ({ page }) => {
|
||||
const csp1 = await (await page.goto(BASE)).headerValue(
|
||||
"content-security-policy",
|
||||
);
|
||||
const csp2 = await (await page.reload()).headerValue(
|
||||
"content-security-policy",
|
||||
);
|
||||
|
||||
expect(csp1, `check if ${csp1} != ${csp2}`).not.toEqual(csp2);
|
||||
});
|
||||
|
||||
test("form component documentation", async ({ page }) => {
|
||||
await page.goto(`${BASE}/component.sql?component=form`);
|
||||
|
||||
// Find the form that contains radio buttons for component selection
|
||||
const componentForm = page.locator("form", {
|
||||
has: page.getByRole("radio", { name: "Chart" }),
|
||||
});
|
||||
|
||||
// the form should be visible
|
||||
await expect(componentForm).toBeVisible();
|
||||
|
||||
// Check that "form" is the first and default selected option
|
||||
const mapRadio = componentForm.getByRole("radio", { name: "Map" });
|
||||
await expect(mapRadio).toHaveValue("map");
|
||||
await expect(mapRadio).toBeChecked();
|
||||
|
||||
// Select "Chart" option and submit
|
||||
await componentForm.getByLabel("Chart").click({ force: true });
|
||||
await componentForm.getByRole("button", { name: "Submit" }).click();
|
||||
|
||||
// Verify we're on the chart documentation page
|
||||
await expect(
|
||||
page.getByRole("heading", { name: /chart/i, level: 1 }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("form select combines initial options with remote search results", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto(`${BASE}/component.sql?component=form`);
|
||||
|
||||
const select = page
|
||||
.locator(
|
||||
'select[data-options_source="examples/from_component_options_source.sql"]',
|
||||
)
|
||||
.first();
|
||||
await expect(select).toBeAttached();
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
!!document.querySelector<HTMLSelectElement>(
|
||||
'select[data-options_source="examples/from_component_options_source.sql"]',
|
||||
)?.tomselect,
|
||||
);
|
||||
|
||||
const initialState = await select.evaluate((element) => {
|
||||
const tomselect = element.tomselect;
|
||||
return {
|
||||
value: tomselect.getValue(),
|
||||
labels: Object.fromEntries(
|
||||
Object.entries(tomselect.options).map(([value, option]) => [
|
||||
value,
|
||||
option.label,
|
||||
]),
|
||||
),
|
||||
};
|
||||
});
|
||||
expect(initialState).toEqual({
|
||||
value: "form",
|
||||
labels: { form: "Form" },
|
||||
});
|
||||
|
||||
await select.evaluate((element) => element.tomselect.focus());
|
||||
await page.keyboard.type("form");
|
||||
await page.waitForResponse((response) =>
|
||||
response
|
||||
.url()
|
||||
.includes("examples/from_component_options_source.sql?search=form"),
|
||||
);
|
||||
await expect
|
||||
.poll(async () =>
|
||||
select.evaluate((element) => ({
|
||||
value: element.tomselect.getValue(),
|
||||
formLabel: element.tomselect.options.form?.label,
|
||||
})),
|
||||
)
|
||||
.toEqual({
|
||||
value: "form",
|
||||
formLabel: "form",
|
||||
});
|
||||
|
||||
await select.evaluate((element) => element.tomselect.setTextboxValue(""));
|
||||
await page.keyboard.type("map");
|
||||
await page.waitForResponse((response) =>
|
||||
response
|
||||
.url()
|
||||
.includes("examples/from_component_options_source.sql?search=map"),
|
||||
);
|
||||
await expect
|
||||
.poll(async () =>
|
||||
select.evaluate((element) => ({
|
||||
value: element.tomselect.getValue(),
|
||||
formLabel: element.tomselect.options.form?.label,
|
||||
mapLabel: element.tomselect.options.map?.label,
|
||||
})),
|
||||
)
|
||||
.toEqual({
|
||||
value: "form",
|
||||
formLabel: "form",
|
||||
mapLabel: "map",
|
||||
});
|
||||
});
|
||||
|
||||
test("modal", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=modal#component`);
|
||||
// get the button that opens the modal
|
||||
const openButton = page.getByRole("button", { name: "Open a simple modal" });
|
||||
await openButton.click();
|
||||
|
||||
const modal = page.getByRole("dialog", { label: "A modal box" });
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// close the modal
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(modal).not.toBeVisible();
|
||||
|
||||
await openButton.click();
|
||||
await expect(modal).toBeVisible();
|
||||
await modal.getByRole("button", { label: "Close" }).first().click();
|
||||
await expect(modal).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("table action buttons - edit_url and delete_url", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".table-responsive", {
|
||||
has: page.getByRole("cell", { name: "PharmaCo" }),
|
||||
});
|
||||
|
||||
const editButton = tableSection.getByTitle("Edit").first();
|
||||
await expect(editButton).toBeVisible();
|
||||
await expect(editButton).toHaveAttribute("href", /action=edit&update_id=\d+/);
|
||||
|
||||
const deleteButton = tableSection.getByTitle("Delete").first();
|
||||
await expect(deleteButton).toBeVisible();
|
||||
await expect(deleteButton).toHaveAttribute(
|
||||
"href",
|
||||
/action=delete&delete_id=\d+/,
|
||||
);
|
||||
});
|
||||
|
||||
test("table action buttons - custom_actions", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".table-responsive", {
|
||||
has: page.getByRole("cell", { name: "PharmaCo" }),
|
||||
});
|
||||
|
||||
const historyButton = tableSection
|
||||
.getByTitle("View Standard History")
|
||||
.first();
|
||||
await expect(historyButton).toBeVisible();
|
||||
await expect(historyButton).toHaveAttribute(
|
||||
"href",
|
||||
/action=history&standard_id=\d+/,
|
||||
);
|
||||
});
|
||||
|
||||
test("table action buttons - _sqlpage_actions", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".table-responsive", {
|
||||
has: page.getByRole("cell", { name: "PharmaCo" }),
|
||||
});
|
||||
|
||||
const pdfButtons = tableSection.getByTitle("View Presentation");
|
||||
await expect(pdfButtons.first()).toBeVisible();
|
||||
await expect(pdfButtons).toHaveCount(3);
|
||||
|
||||
const firstPdfButton = pdfButtons.first();
|
||||
await expect(firstPdfButton).toHaveAttribute(
|
||||
"href",
|
||||
"https://sql-page.com/pgconf/2024-sqlpage-badass.pdf",
|
||||
);
|
||||
|
||||
const setInUseButton = tableSection.getByTitle("Set In Use");
|
||||
await expect(setInUseButton).toBeVisible();
|
||||
await expect(setInUseButton).toHaveAttribute(
|
||||
"href",
|
||||
/action=set_in_use&standard_id=32/,
|
||||
);
|
||||
|
||||
const retireButton = tableSection.getByTitle("Retire Standard");
|
||||
await expect(retireButton).toBeVisible();
|
||||
await expect(retireButton).toHaveAttribute(
|
||||
"href",
|
||||
/action=retire&standard_id=33/,
|
||||
);
|
||||
});
|
||||
|
||||
test("table action buttons - disabled action", async ({ page }) => {
|
||||
await page.goto(`${BASE}/documentation.sql?component=table`);
|
||||
const tableSection = page.locator(".table-responsive", {
|
||||
has: page.getByRole("cell", { name: "PharmaCo" }),
|
||||
});
|
||||
|
||||
const viewPresentationButtons = tableSection.getByTitle("View Presentation");
|
||||
await expect(viewPresentationButtons).toHaveCount(3);
|
||||
|
||||
const actionColumnButtons = tableSection.locator(
|
||||
"td._col_Action a[data-action='Action']",
|
||||
);
|
||||
await expect(actionColumnButtons).toHaveCount(3);
|
||||
|
||||
const emptyActionButton = actionColumnButtons.last();
|
||||
await expect(emptyActionButton).toHaveAttribute("href", "null");
|
||||
await expect(emptyActionButton).toHaveAttribute("title", "Action");
|
||||
});
|
||||
Generated
+97
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"name": "end-to-end",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "end-to-end",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@types/node": "^25.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
|
||||
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "end-to-end",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "playwright test --reporter=line",
|
||||
"gui": "playwright test --ui"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@types/node": "^25.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// import dotenv from 'dotenv';
|
||||
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: "./.",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
||||
Reference in New Issue
Block a user