This commit is contained in:
d-kimsuon
2025-10-12 21:00:12 +09:00
parent 584c0409d3
commit e0983a7b92
8 changed files with 147 additions and 97 deletions

View File

@@ -1,21 +1,21 @@
import { resolve } from "node:path";
import { withPlaywright } from "../utils/withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "../utils/withPlaywright";
// Different error scenarios to capture
const errorScenarios = [
{
name: "404",
url: "http://localhost:4000/non-existent-page"
url: "http://localhost:4000/non-existent-page",
},
{
name: "invalid-project",
url: "http://localhost:4000/projects/non-existent-project"
url: "http://localhost:4000/projects/non-existent-project",
},
{
name: "invalid-session",
url: "http://localhost:4000/projects/sample-project/sessions/non-existent-session"
}
url: "http://localhost:4000/projects/sample-project/sessions/non-existent-session",
},
];
for (const scenario of errorScenarios) {
@@ -25,7 +25,12 @@ for (const scenario of errorScenarios) {
const page = await context.newPage();
await page.goto(scenario.url);
await page.screenshot({
path: resolve("e2e", "snapshots", "errors", `${scenario.name}_${name}.png`),
path: resolve(
"e2e",
"snapshots",
"errors",
`${scenario.name}_${name}.png`,
),
fullPage: true,
});
await cleanUp();
@@ -37,4 +42,4 @@ for (const scenario of errorScenarios) {
},
);
}
}
}

View File

@@ -1,13 +1,13 @@
import { resolve } from "node:path";
import { withPlaywright } from "../utils/withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "../utils/withPlaywright";
for (const { device, name } of testDevices) {
await withPlaywright(
async ({ context, cleanUp }) => {
const page = await context.newPage();
await page.goto("http://localhost:4000/");
await page.waitForLoadState('networkidle');
await page.waitForLoadState("networkidle");
await page.screenshot({
path: resolve("e2e", "snapshots", "root", `${name}.png`),
fullPage: true,
@@ -20,4 +20,4 @@ for (const { device, name } of testDevices) {
},
},
);
}
}

View File

@@ -1,5 +1,5 @@
import { execSync } from "node:child_process";
import { resolve, dirname } from "node:path";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
@@ -7,32 +7,34 @@ const __dirname = dirname(__filename);
const scripts = [
"home.ts",
"projects.ts",
"projects.ts",
"project-detail.ts",
"session-detail.ts",
"error-pages.ts"
"error-pages.ts",
];
async function captureAllSnapshots() {
console.log("🚀 Starting screenshot capture for all pages...\n");
for (const script of scripts) {
const scriptPath = resolve(__dirname, script);
console.log(`📸 Capturing: ${script.replace('.ts', '')}...`);
console.log(`📸 Capturing: ${script.replace(".ts", "")}...`);
try {
// Execute each script using tsx
execSync(`npx tsx "${scriptPath}"`, {
stdio: 'inherit',
cwd: resolve(__dirname, "..", "..")
execSync(`npx tsx "${scriptPath}"`, {
stdio: "inherit",
cwd: resolve(__dirname, "..", ".."),
});
console.log(`✅ Completed: ${script.replace('.ts', '')}\n`);
console.log(`✅ Completed: ${script.replace(".ts", "")}\n`);
} catch (error) {
console.error(`❌ Failed: ${script.replace('.ts', '')} - ${error.message}\n`);
console.error(
`❌ Failed: ${script.replace(".ts", "")} - ${error.message}\n`,
);
}
}
console.log("🎉 All screenshot captures completed!");
}
captureAllSnapshots().catch(console.error);
captureAllSnapshots().catch(console.error);

View File

@@ -1,24 +1,34 @@
import { resolve } from "node:path";
import { withPlaywright } from "../utils/withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "../utils/withPlaywright";
// Test different UI states on project detail page
const testStates = [
{ name: 'default', action: null },
{ name: 'filters-expanded', action: async (page) => {
const filterToggle = page.locator('[data-testid="filter-toggle"], button:has-text("Filters"), .filter-toggle');
if (await filterToggle.isVisible()) {
await filterToggle.click();
await page.waitForTimeout(300);
}
}},
{ name: 'new-chat-modal', action: async (page) => {
const newChatButton = page.locator('button:has-text("New Chat"), [data-testid="new-chat"], .new-chat-button');
if (await newChatButton.isVisible()) {
await newChatButton.click();
await page.waitForTimeout(300);
}
}}
{ name: "default", action: null },
{
name: "filters-expanded",
action: async (page) => {
const filterToggle = page.locator(
'[data-testid="filter-toggle"], button:has-text("Filters"), .filter-toggle',
);
if (await filterToggle.isVisible()) {
await filterToggle.click();
await page.waitForTimeout(300);
}
},
},
{
name: "new-chat-modal",
action: async (page) => {
const newChatButton = page.locator(
'button:has-text("New Chat"), [data-testid="new-chat"], .new-chat-button',
);
if (await newChatButton.isVisible()) {
await newChatButton.click();
await page.waitForTimeout(300);
}
},
},
];
for (const state of testStates) {
@@ -27,14 +37,20 @@ for (const state of testStates) {
async ({ context, cleanUp }) => {
const page = await context.newPage();
await page.goto("http://localhost:4000/projects/sample-project");
await page.waitForLoadState('networkidle');
await page.waitForLoadState("networkidle");
if (state.action) {
await state.action(page);
}
await page.screenshot({
path: resolve("e2e", "snapshots", "project-detail", state.name, `${name}.png`),
path: resolve(
"e2e",
"snapshots",
"project-detail",
state.name,
`${name}.png`,
),
fullPage: true,
});
await cleanUp();
@@ -46,4 +62,4 @@ for (const state of testStates) {
},
);
}
}
}

View File

@@ -1,14 +1,17 @@
import { resolve } from "node:path";
import { withPlaywright } from "../utils/withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "../utils/withPlaywright";
// Test different states on projects page
const testStates = [
{ name: 'default', action: null },
{ name: 'empty', action: async (page) => {
// Check for empty state (this will capture whatever state exists)
await page.waitForTimeout(500);
}}
{ name: "default", action: null },
{
name: "empty",
action: async (page) => {
// Check for empty state (this will capture whatever state exists)
await page.waitForTimeout(500);
},
},
];
for (const state of testStates) {
@@ -17,14 +20,20 @@ for (const state of testStates) {
async ({ context, cleanUp }) => {
const page = await context.newPage();
await page.goto("http://localhost:4000/projects");
await page.waitForLoadState('networkidle');
await page.waitForLoadState("networkidle");
if (state.action) {
await state.action(page);
}
await page.screenshot({
path: resolve("e2e", "snapshots", "projects", state.name, `${name}.png`),
path: resolve(
"e2e",
"snapshots",
"projects",
state.name,
`${name}.png`,
),
fullPage: true,
});
await cleanUp();
@@ -36,4 +45,4 @@ for (const state of testStates) {
},
);
}
}
}

View File

@@ -1,24 +1,29 @@
import { resolve } from "node:path";
import { withPlaywright } from "../utils/withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "../utils/withPlaywright";
// Multiple session IDs to capture different session detail pages
// Multiple session IDs to capture different session detail pages
const sessionIds = [
"1af7fc5e-8455-4414-9ccd-011d40f70b2a",
"5c0375b4-57a5-4f26-b12d-d022ee4e51b7",
"fe5e1c67-53e7-4862-81ae-d0e013e3270b"
"5c0375b4-57a5-4f26-b12d-d022ee4e51b7",
"fe5e1c67-53e7-4862-81ae-d0e013e3270b",
];
// Test different sidebar states
// Test different sidebar states
const testStates = [
{ name: 'default', action: null },
{ name: 'sidebar-open', action: async (page) => {
const menuButton = page.locator('[data-testid="menu-button"], button:has-text("Menu"), .menu-toggle, .hamburger');
if (await menuButton.isVisible()) {
await menuButton.click();
await page.waitForTimeout(300);
}
}}
{ name: "default", action: null },
{
name: "sidebar-open",
action: async (page) => {
const menuButton = page.locator(
'[data-testid="menu-button"], button:has-text("Menu"), .menu-toggle, .hamburger',
);
if (await menuButton.isVisible()) {
await menuButton.click();
await page.waitForTimeout(300);
}
},
},
];
for (const sessionId of sessionIds) {
@@ -27,16 +32,24 @@ for (const sessionId of sessionIds) {
await withPlaywright(
async ({ context, cleanUp }) => {
const page = await context.newPage();
await page.goto(`http://localhost:4000/projects/sample-project/sessions/${sessionId}`);
await page.waitForLoadState('networkidle');
await page.goto(
`http://localhost:4000/projects/sample-project/sessions/${sessionId}`,
);
await page.waitForLoadState("networkidle");
if (state.action) {
await state.action(page);
}
// Create separate directories for each session
await page.screenshot({
path: resolve("e2e", "snapshots", "session-detail", state.name, `${sessionId}_${name}.png`),
path: resolve(
"e2e",
"snapshots",
"session-detail",
state.name,
`${sessionId}_${name}.png`,
),
fullPage: true,
});
await cleanUp();
@@ -49,4 +62,4 @@ for (const sessionId of sessionIds) {
);
}
}
}
}

View File

@@ -1,6 +1,6 @@
import { resolve } from "node:path";
import { withPlaywright } from "./utils/withPlaywright";
import { testDevices } from "./testDevices";
import { withPlaywright } from "./utils/withPlaywright";
for (const { device, name } of testDevices) {
await withPlaywright(

View File

@@ -1,6 +1,6 @@
import { resolve } from "node:path";
import { withPlaywright } from "./withPlaywright";
import { testDevices } from "../testDevices";
import { withPlaywright } from "./withPlaywright";
/**
* Take screenshots for a given URL across all test devices
@@ -11,7 +11,7 @@ export async function takeScreenshots(
options?: {
waitForSelector?: string;
timeout?: number;
}
},
) {
const { waitForSelector, timeout = 5000 } = options || {};
@@ -19,21 +19,21 @@ export async function takeScreenshots(
await withPlaywright(
async ({ context, cleanUp }) => {
const page = await context.newPage();
try {
await page.goto(url);
// Wait for the page to load
await page.waitForLoadState('networkidle');
await page.waitForLoadState("networkidle");
// Wait for specific selector if provided
if (waitForSelector) {
await page.waitForSelector(waitForSelector, { timeout });
}
// Additional wait for content to stabilize
await page.waitForTimeout(1000);
// Hide dynamic content that might cause flaky screenshots
await page.addStyleTag({
content: `
@@ -51,9 +51,9 @@ export async function takeScreenshots(
transition-duration: 0s !important;
transition-delay: 0s !important;
}
`
`,
});
await page.screenshot({
path: resolve("e2e", "snapshots", snapshotName, `${name}.png`),
fullPage: true,
@@ -66,7 +66,7 @@ export async function takeScreenshots(
contextOptions: {
...device,
},
}
},
);
}
}
@@ -80,7 +80,7 @@ export async function takeElementScreenshots(
snapshotName: string,
options?: {
timeout?: number;
}
},
) {
const { timeout = 5000 } = options || {};
@@ -88,25 +88,30 @@ export async function takeElementScreenshots(
await withPlaywright(
async ({ context, cleanUp }) => {
const page = await context.newPage();
try {
await page.goto(url);
await page.waitForLoadState('networkidle');
await page.waitForLoadState("networkidle");
const element = page.locator(selector);
await element.waitFor({ state: 'visible', timeout });
await element.waitFor({ state: "visible", timeout });
await page.addStyleTag({
content: `
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
`
`,
});
await element.screenshot({
path: resolve("e2e", "snapshots", snapshotName, `${name}-element.png`),
path: resolve(
"e2e",
"snapshots",
snapshotName,
`${name}-element.png`,
),
});
} finally {
await cleanUp();
@@ -116,7 +121,7 @@ export async function takeElementScreenshots(
contextOptions: {
...device,
},
}
},
);
}
}
}