Skip to content

Commit 01770d2

Browse files
committed
chore(e2e): reorganize specs by feature
1 parent 9fce5af commit 01770d2

60 files changed

Lines changed: 1354 additions & 1170 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

e2e/fixtures/app-entry.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { expect, type Locator, type Page } from "@playwright/test";
2+
import { translatePatternForE2E } from "./i18n.js";
3+
import {
4+
expectOpenWorkspaceButton,
5+
expectSettingsButton,
6+
expectWelcomeCopy,
7+
} from "./phase1-i18n.js";
8+
9+
export const APP_ENTRY_SELECTOR =
10+
".welcome-container, .workspace-page, .agent-draft-launcher, .session-card.agent-pane[data-session-id]";
11+
12+
export async function expectAppEntry(page: Page): Promise<void> {
13+
await expect(page.locator(APP_ENTRY_SELECTOR).first()).toBeVisible();
14+
}
15+
16+
export async function isWelcomeVisible(page: Page): Promise<boolean> {
17+
return await page
18+
.locator(".welcome-container")
19+
.isVisible()
20+
.catch(() => false);
21+
}
22+
23+
export async function expectWelcomeCardIfVisible(page: Page): Promise<void> {
24+
await expectAppEntry(page);
25+
if (await isWelcomeVisible(page)) {
26+
await expect(page.locator(".welcome-card")).toBeVisible();
27+
}
28+
}
29+
30+
export async function expectWelcomeCopyIfVisible(page: Page): Promise<void> {
31+
await expectAppEntry(page);
32+
if (await isWelcomeVisible(page)) {
33+
await expectWelcomeCopy(page);
34+
}
35+
}
36+
37+
export async function expectPrimaryWorkspaceAction(page: Page): Promise<Locator> {
38+
await expectAppEntry(page);
39+
40+
const welcomeButton = page.locator(".welcome-btn").first();
41+
if (await welcomeButton.isVisible().catch(() => false)) {
42+
await expectOpenWorkspaceButton(welcomeButton);
43+
return welcomeButton;
44+
}
45+
46+
const newWorkspaceButton = page
47+
.getByRole("button", {
48+
name: translatePatternForE2E("tooltip.new_workspace"),
49+
})
50+
.first();
51+
await expect(newWorkspaceButton).toBeVisible();
52+
return newWorkspaceButton;
53+
}
54+
55+
export async function expectSettingsEntryPoint(page: Page): Promise<Locator> {
56+
await expectAppEntry(page);
57+
58+
const welcomeSettings = page.locator(".welcome-link").first();
59+
if (await welcomeSettings.isVisible().catch(() => false)) {
60+
await expectSettingsButton(welcomeSettings);
61+
return welcomeSettings;
62+
}
63+
64+
const settingsButton = page
65+
.getByRole("button", {
66+
name: translatePatternForE2E("action.settings"),
67+
})
68+
.first();
69+
await expect(settingsButton).toBeVisible();
70+
return settingsButton;
71+
}

e2e/fixtures/i18n.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,17 @@ export function translateForE2E(
5858

5959
return text;
6060
}
61+
62+
function escapeRegExp(value: string): string {
63+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
64+
}
65+
66+
export function translatePatternForE2E(
67+
key: E2ETranslationKey,
68+
params?: Record<string, string | number>
69+
): RegExp {
70+
const en = translateForE2E(key, "en", params);
71+
const zh = translateForE2E(key, "zh", params);
72+
const values = [...new Set([en, zh])].map(escapeRegExp);
73+
return new RegExp(`^(?:${values.join("|")})$`);
74+
}

e2e/playwright.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const sandboxDir =
1111
mkdtempSync(join(tmpdir(), "coder-studio-phase1-acceptance-"));
1212
const dataDir = process.env.CODER_STUDIO_PHASE1_DATA_DIR ?? join(sandboxDir, "coder-studio.db");
1313
const runtimeDir = process.env.CODER_STUDIO_PHASE1_RUNTIME_DIR ?? join(sandboxDir, "runtime");
14+
const providerMockDir =
15+
process.env.CODER_STUDIO_E2E_PROVIDER_MOCK_DIR ?? join(sandboxDir, "provider-mock");
16+
const providerMockBinDir = join(providerMockDir, "bin");
17+
const providerMockStatePath = join(providerMockDir, "state.json");
18+
const providerMockDebugLogPath = join(providerMockDir, "debug.log");
1419

1520
async function reservePort(host: string): Promise<number> {
1621
return await new Promise<number>((resolve, reject) => {
@@ -53,6 +58,7 @@ if (ownsPhase1Sandbox) {
5358
process.env.CODER_STUDIO_PHASE1_RUNTIME_DIR = runtimeDir;
5459
process.env.CODER_STUDIO_PHASE1_SERVER_PORT = String(SERVER_PORT);
5560
process.env.CODER_STUDIO_PHASE1_WEB_PORT = String(WEB_PORT);
61+
process.env.CODER_STUDIO_E2E_PROVIDER_MOCK_DIR = providerMockDir;
5662

5763
process.on("exit", () => {
5864
rmSync(sandboxDir, { recursive: true, force: true });
@@ -80,6 +86,10 @@ export default defineConfig({
8086
DATA_DIR: dataDir,
8187
RUNTIME_DIR: runtimeDir,
8288
NO_AUTH: "true",
89+
CODER_STUDIO_E2E_PROVIDER_STATE_PATH: providerMockStatePath,
90+
CODER_STUDIO_E2E_PROVIDER_BIN_DIR: providerMockBinDir,
91+
CODER_STUDIO_E2E_PROVIDER_DEBUG_LOG_PATH: providerMockDebugLogPath,
92+
PATH: `${providerMockBinDir}:${process.env.PATH ?? ""}`,
8393
},
8494
},
8595
{
File renamed without changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { expect, test } from "@playwright/test";
2+
import { expectAppEntry, expectWelcomeCopyIfVisible } from "../../fixtures/app-entry";
3+
4+
test.describe("@phase1 focus mode acceptance", () => {
5+
test("F1-27 enter focus", async ({ page }) => {
6+
await page.goto("/");
7+
// Smoke check that the root shell resolves.
8+
await expectAppEntry(page);
9+
});
10+
11+
test("F1-28 exit focus", async ({ page }) => {
12+
await page.goto("/");
13+
// Only assert welcome copy when "/" lands on the welcome shell.
14+
await expectWelcomeCopyIfVisible(page);
15+
});
16+
});
Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, test } from "@playwright/test";
2+
import { expectAppEntry, isWelcomeVisible } from "../../fixtures/app-entry";
23
import { translateForE2E } from "../../fixtures/i18n";
34
import {
45
openSettingsSection,
@@ -36,8 +37,8 @@ test.describe("@phase2 i18n acceptance", () => {
3637
test("P2I-03 all UI text uses translation", async ({ page }) => {
3738
await page.goto("/");
3839

39-
// Check that welcome screen text is visible (uses translation)
40-
await expect(page.locator(".welcome-container")).toBeVisible();
40+
// "/" may restore the last workspace instead of showing welcome.
41+
await expectAppEntry(page);
4142

4243
// Navigate to settings
4344
await page.goto("/settings");
@@ -47,8 +48,10 @@ test.describe("@phase2 i18n acceptance", () => {
4748
test("P2I-04 fallback to default language", async ({ page }) => {
4849
await page.goto("/");
4950

50-
// Welcome screen should show content
51-
await expect(page.locator(".welcome-container")).toBeVisible();
52-
await expect(page.locator(".welcome-title")).toBeVisible();
51+
// The root shell should render, and welcome title should exist when welcome is active.
52+
await expectAppEntry(page);
53+
if (await isWelcomeVisible(page)) {
54+
await expect(page.locator(".welcome-title")).toBeVisible();
55+
}
5356
});
5457
});
File renamed without changes.

e2e/specs/session-terminal-interaction.spec.ts renamed to e2e/specs/app-shell/session-terminal-interaction.spec.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import * as fs from "node:fs";
2+
import { dirname, join } from "node:path";
3+
import { fileURLToPath } from "node:url";
14
import { expect, test } from "@playwright/test";
2-
import * as fs from "fs";
35

4-
const SCREENSHOTS_DIR = "/home/spencer/workspace/coder-studio/e2e-screenshots";
6+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
7+
const SCREENSHOTS_DIR = join(REPO_ROOT, "e2e-screenshots");
58

69
function ensureDir() {
710
if (!fs.existsSync(SCREENSHOTS_DIR)) {
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import { expect, test } from "@playwright/test";
2+
import { expectAppEntry, expectWelcomeCardIfVisible } from "../../fixtures/app-entry";
23

34
test.describe("@phase1 websocket acceptance", () => {
45
test("F1-29 connect", async ({ page }) => {
56
await page.goto("/");
6-
// Page loads correctly
7-
await expect(page.locator(".welcome-container")).toBeVisible();
7+
// Root shell should load regardless of restore path.
8+
await expectAppEntry(page);
89
});
910

1011
test("F1-30 message flow", async ({ page }) => {
1112
await page.goto("/");
12-
// Check welcome card
13-
await expect(page.locator(".welcome-card")).toBeVisible();
13+
// Validate welcome chrome only when the welcome shell is active.
14+
await expectWelcomeCardIfVisible(page);
1415
});
1516

1617
test("F1-31 reconnect", async ({ page }) => {

0 commit comments

Comments
 (0)