Skip to content

Commit 18b45bd

Browse files
committed
test(view): add E2E test infrastructure and new scenarios
Fully implement New Scenarios (N1, N2, N3) E2E tests - ErrorBoundary recovery test (TestErrorTrigger component added) - Communication timeout test (Mock response delay/silent mode) - Large dataset performance test (100 commands rendering) Add mock test control features: - setResponseDelay/resetResponseDelay: control response delay - addSilentMessageType/clearSilentMessageTypes: silent mode for timeout testing - Expose window.__vscodeMock for E2E test access
1 parent 91b0696 commit 18b45bd

6 files changed

Lines changed: 576 additions & 15 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test.describe("Test N2: VS Code Communication Timeout", () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto("/");
6+
await page.waitForLoadState("networkidle");
7+
});
8+
9+
test.afterEach(async ({ page }) => {
10+
// Clean up: reset mock settings
11+
await page.evaluate(() => {
12+
const mock = (window as Window & { __vscodeMock?: { clearSilentMessageTypes: () => void; resetResponseDelay: () => void } }).__vscodeMock;
13+
if (mock) {
14+
mock.clearSilentMessageTypes();
15+
mock.resetResponseDelay();
16+
}
17+
});
18+
});
19+
20+
// Note: saveConfig in dev mode uses synchronous setCurrentData, not sendMessage
21+
// So we test timeout with operations that actually use sendMessage
22+
23+
test("should show timeout error toast when button set creation times out", async ({ page }) => {
24+
// Given: Set mock to not respond to createButtonSet messages
25+
await page.evaluate(() => {
26+
const mock = (window as Window & { __vscodeMock?: { addSilentMessageType: (type: string) => void } }).__vscodeMock;
27+
if (mock) {
28+
mock.addSilentMessageType("createButtonSet");
29+
}
30+
});
31+
32+
// When: Try to create a new button set
33+
await page.getByRole("button", { name: "Button Set" }).click();
34+
await page.getByRole("menuitem", { name: "Create new set" }).click();
35+
await page.getByRole("textbox", { name: "Set Name" }).fill("Timeout Test Set");
36+
await page.getByRole("button", { name: "Create" }).click();
37+
38+
// Then: Timeout error toast should appear
39+
const toast = page.locator("[data-sonner-toast]", {
40+
hasText: "Communication with extension timed out",
41+
});
42+
await expect(toast).toBeVisible({ timeout: 7000 });
43+
});
44+
45+
test("should recover after timeout and allow retry", async ({ page }) => {
46+
// Given: Set mock to not respond to createButtonSet first
47+
await page.evaluate(() => {
48+
const mock = (window as Window & { __vscodeMock?: { addSilentMessageType: (type: string) => void } }).__vscodeMock;
49+
if (mock) {
50+
mock.addSilentMessageType("createButtonSet");
51+
}
52+
});
53+
54+
// When: Try to create button set and wait for timeout
55+
await page.getByRole("button", { name: "Button Set" }).click();
56+
await page.getByRole("menuitem", { name: "Create new set" }).click();
57+
await page.getByRole("textbox", { name: "Set Name" }).fill("Retry Test Set");
58+
await page.getByRole("button", { name: "Create" }).click();
59+
60+
const timeoutToast = page.locator("[data-sonner-toast]", {
61+
hasText: "timed out",
62+
});
63+
await expect(timeoutToast).toBeVisible({ timeout: 7000 });
64+
65+
// Clear silent mode
66+
await page.evaluate(() => {
67+
const mock = (window as Window & { __vscodeMock?: { clearSilentMessageTypes: () => void } }).__vscodeMock;
68+
if (mock) {
69+
mock.clearSilentMessageTypes();
70+
}
71+
});
72+
73+
// Wait for timeout toast to disappear
74+
await timeoutToast.waitFor({ state: "hidden", timeout: 10000 });
75+
76+
// Retry - should succeed now
77+
await page.getByRole("button", { name: "Create" }).click();
78+
79+
// Then: Success toast should appear
80+
const successToast = page.locator("[data-sonner-toast]", {
81+
hasText: 'Button set "Retry Test Set" created',
82+
});
83+
await expect(successToast).toBeVisible({ timeout: 3000 });
84+
});
85+
86+
test("should log timeout error to console", async ({ page }) => {
87+
// Set up console listener
88+
const consoleMessages: string[] = [];
89+
page.on("console", (msg) => {
90+
if (msg.type() === "error") {
91+
consoleMessages.push(msg.text());
92+
}
93+
});
94+
95+
// Given: Set mock to not respond
96+
await page.evaluate(() => {
97+
const mock = (window as Window & { __vscodeMock?: { addSilentMessageType: (type: string) => void } }).__vscodeMock;
98+
if (mock) {
99+
mock.addSilentMessageType("createButtonSet");
100+
}
101+
});
102+
103+
// When: Try to create button set
104+
await page.getByRole("button", { name: "Button Set" }).click();
105+
await page.getByRole("menuitem", { name: "Create new set" }).click();
106+
await page.getByRole("textbox", { name: "Set Name" }).fill("Console Test");
107+
await page.getByRole("button", { name: "Create" }).click();
108+
109+
// Wait for timeout
110+
await page.waitForTimeout(6000);
111+
112+
// Then: Error should be logged
113+
expect(consoleMessages.some((msg) => msg.includes("Communication with extension timed out"))).toBe(true);
114+
});
115+
116+
test("should handle slow but successful response", async ({ page }) => {
117+
// Given: Set mock to respond slowly (but within timeout)
118+
await page.evaluate(() => {
119+
const mock = (window as Window & { __vscodeMock?: { setResponseDelay: (delay: number) => void } }).__vscodeMock;
120+
if (mock) {
121+
mock.setResponseDelay(2000); // 2 seconds delay
122+
}
123+
});
124+
125+
// When: Create a button set (uses sendMessage unlike save)
126+
await page.getByRole("button", { name: "Button Set" }).click();
127+
await page.getByRole("menuitem", { name: "Create new set" }).click();
128+
await page.getByRole("textbox", { name: "Set Name" }).fill("Slow Response Test");
129+
await page.getByRole("button", { name: "Create" }).click();
130+
131+
// Then: Should eventually show success (not timeout)
132+
const successToast = page.locator("[data-sonner-toast]", {
133+
hasText: 'Button set "Slow Response Test" created',
134+
});
135+
await expect(successToast).toBeVisible({ timeout: 5000 });
136+
137+
// No timeout toast should appear
138+
const timeoutToast = page.locator("[data-sonner-toast]", {
139+
hasText: "timed out",
140+
});
141+
await expect(timeoutToast).not.toBeVisible();
142+
});
143+
});
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { expect, test } from "@playwright/test";
2+
3+
test.describe("Test N1: Error Boundary Recovery", () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto("/");
6+
await page.waitForLoadState("networkidle");
7+
});
8+
9+
test("should display error boundary fallback when error occurs", async ({ page }) => {
10+
// Given: App is loaded normally
11+
const header = page.locator("header");
12+
await expect(header).toBeVisible();
13+
14+
// When: Trigger an error via test button
15+
const triggerButton = page.getByTestId("test-error-trigger");
16+
await expect(triggerButton).toBeVisible();
17+
await triggerButton.click();
18+
19+
// Then: Error boundary fallback should be displayed
20+
const fallback = page.getByTestId("error-boundary-fallback");
21+
await expect(fallback).toBeVisible();
22+
23+
// Verify error message is shown
24+
const errorMessage = page.getByTestId("error-message");
25+
await expect(errorMessage).toContainText("Test error triggered for E2E testing");
26+
27+
// Verify title is shown
28+
await expect(page.getByText("Something went wrong")).toBeVisible();
29+
});
30+
31+
test("should recover when reset button is clicked", async ({ page }) => {
32+
// Given: Trigger an error first
33+
const triggerButton = page.getByTestId("test-error-trigger");
34+
await triggerButton.click();
35+
36+
// Verify error state
37+
const fallback = page.getByTestId("error-boundary-fallback");
38+
await expect(fallback).toBeVisible();
39+
40+
// When: Click reset button
41+
const resetButton = page.getByTestId("error-boundary-reset");
42+
await expect(resetButton).toBeVisible();
43+
await resetButton.click();
44+
45+
// Then: App should recover and show normal UI
46+
await expect(fallback).not.toBeVisible();
47+
48+
// Header should be visible again
49+
const header = page.locator("header");
50+
await expect(header).toBeVisible();
51+
52+
// Command list should be visible
53+
const commandList = page.locator('[data-testid="command-card"]');
54+
await expect(commandList.first()).toBeVisible();
55+
});
56+
57+
test("should show error boundary fallback in Korean when language is Korean", async ({
58+
page,
59+
}) => {
60+
// Given: Switch to Korean
61+
await page.getByRole("button", { name: /language|/i }).click();
62+
await page.getByText("한국어").click();
63+
64+
// Wait for language change
65+
await page.waitForTimeout(300);
66+
67+
// When: Trigger an error
68+
const triggerButton = page.getByTestId("test-error-trigger");
69+
await triggerButton.click();
70+
71+
// Then: Error boundary should show Korean text
72+
await expect(page.getByText("문제가 발생했습니다")).toBeVisible();
73+
await expect(page.getByText("구성 UI에서 오류가 발생했습니다")).toBeVisible();
74+
});
75+
76+
test("should log error to console when error occurs", async ({ page }) => {
77+
// Given: Set up console listener
78+
const consoleMessages: string[] = [];
79+
page.on("console", (msg) => {
80+
if (msg.type() === "error") {
81+
consoleMessages.push(msg.text());
82+
}
83+
});
84+
85+
// When: Trigger an error
86+
const triggerButton = page.getByTestId("test-error-trigger");
87+
await triggerButton.click();
88+
89+
// Wait for error boundary to display
90+
await expect(page.getByTestId("error-boundary-fallback")).toBeVisible();
91+
92+
// Then: Error should be logged
93+
expect(consoleMessages.some((msg) => msg.includes("Webview crashed"))).toBe(true);
94+
});
95+
96+
test("should be able to trigger error again after recovery", async ({ page }) => {
97+
// First error and recovery cycle
98+
const triggerButton = page.getByTestId("test-error-trigger");
99+
await triggerButton.click();
100+
101+
const fallback = page.getByTestId("error-boundary-fallback");
102+
await expect(fallback).toBeVisible();
103+
104+
await page.getByTestId("error-boundary-reset").click();
105+
await expect(fallback).not.toBeVisible();
106+
107+
// Second error cycle
108+
await expect(triggerButton).toBeVisible();
109+
await triggerButton.click();
110+
111+
await expect(fallback).toBeVisible();
112+
await expect(page.getByTestId("error-message")).toContainText("Test error triggered");
113+
});
114+
});

0 commit comments

Comments
 (0)