Skip to content

Commit 7ebffd2

Browse files
committed
Improves e2e smoke tests
1 parent 9bbe42d commit 7ebffd2

File tree

12 files changed

+1014
-120
lines changed

12 files changed

+1014
-120
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import type { Locator, Page } from '@playwright/test';
2+
import { MaxTimeout } from '../../specs/baseTest';
3+
4+
/**
5+
* Component for VS Code Activity Bar interactions.
6+
* The activity bar contains icons for Explorer, Search, Git, Extensions, and custom views.
7+
*/
8+
export class ActivityBar {
9+
private readonly container: Locator;
10+
11+
constructor(private readonly page: Page) {
12+
this.container = page.locator('[id="workbench.parts.activitybar"]');
13+
}
14+
15+
/**
16+
* Get the activity bar container
17+
*/
18+
get locator(): Locator {
19+
return this.container;
20+
}
21+
22+
/**
23+
* Get a tab by its accessible name
24+
*/
25+
getTab(name: string | RegExp, exact = true): Locator {
26+
return this.container.getByRole('tab', { name: name, exact: exact });
27+
}
28+
29+
/**
30+
* Get all tabs matching a pattern
31+
*/
32+
getTabs(name: string | RegExp): Locator {
33+
return this.container.getByRole('tab', { name: name });
34+
}
35+
36+
/**
37+
* Click a tab to activate its view
38+
*/
39+
async clickTab(name: string | RegExp, exact = true): Promise<void> {
40+
await this.getTab(name, exact).click();
41+
}
42+
43+
/**
44+
* Open a tab's view, only clicking if not already active.
45+
* This prevents accidentally closing an already-open sidebar.
46+
*/
47+
async openTab(name: string | RegExp, exact = true): Promise<void> {
48+
const tab = this.getTab(name, exact);
49+
const isActive = await tab.getAttribute('aria-checked');
50+
if (isActive !== 'true') {
51+
await tab.click();
52+
}
53+
}
54+
55+
/**
56+
* Check if a tab is visible
57+
*/
58+
async isTabVisible(name: string | RegExp, exact = true): Promise<boolean> {
59+
return this.getTab(name, exact).isVisible();
60+
}
61+
62+
/**
63+
* Wait for a tab to appear
64+
*/
65+
async waitForTab(name: string | RegExp, exact = true, timeout = MaxTimeout): Promise<void> {
66+
await this.getTab(name, exact).waitFor({ state: 'visible', timeout: timeout });
67+
}
68+
69+
/**
70+
* Count tabs matching a pattern
71+
*/
72+
async countTabs(name: string | RegExp): Promise<number> {
73+
return this.getTabs(name).count();
74+
}
75+
76+
// Common VS Code activity bar tabs
77+
get explorerTab(): Locator {
78+
return this.getTab(/Explorer/);
79+
}
80+
81+
get searchTab(): Locator {
82+
return this.getTab(/Search/);
83+
}
84+
85+
get sourceControlTab(): Locator {
86+
return this.getTab(/Source Control/);
87+
}
88+
89+
get runAndDebugTab(): Locator {
90+
return this.getTab(/Run and Debug/);
91+
}
92+
93+
get extensionsTab(): Locator {
94+
return this.getTab(/Extensions/);
95+
}
96+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import type { Locator, Page } from '@playwright/test';
2+
import { MaxTimeout } from '../../specs/baseTest';
3+
4+
/**
5+
* Component for VS Code Command Palette interactions.
6+
*/
7+
export class CommandPalette {
8+
constructor(private readonly page: Page) {}
9+
10+
/**
11+
* Get the command palette input field
12+
*/
13+
get input(): Locator {
14+
return this.page.locator('.quick-input-box input');
15+
}
16+
17+
/**
18+
* Get the results list
19+
*/
20+
get results(): Locator {
21+
return this.page.locator('.quick-input-list .quick-input-list-row');
22+
}
23+
24+
/**
25+
* Get the first result
26+
*/
27+
get firstResult(): Locator {
28+
return this.results.first();
29+
}
30+
31+
/**
32+
* Get the quick input widget (container for command palette)
33+
*/
34+
get widget(): Locator {
35+
return this.page.locator('.quick-input-widget');
36+
}
37+
38+
/**
39+
* Check if the command palette is open
40+
*/
41+
async isOpen(): Promise<boolean> {
42+
return this.widget.isVisible();
43+
}
44+
45+
/**
46+
* Open the command palette
47+
*/
48+
async open(): Promise<void> {
49+
// Ensure any previous quick input is closed first
50+
if (await this.isOpen()) {
51+
await this.page.keyboard.press('Escape');
52+
await this.widget.waitFor({ state: 'hidden', timeout: MaxTimeout });
53+
}
54+
await this.page.keyboard.press('Control+Shift+P');
55+
await this.input.waitFor({ state: 'visible', timeout: MaxTimeout });
56+
}
57+
58+
/**
59+
* Close the command palette
60+
*/
61+
async close(): Promise<void> {
62+
if (await this.isOpen()) {
63+
await this.page.keyboard.press('Escape');
64+
await this.widget.waitFor({ state: 'hidden', timeout: MaxTimeout });
65+
}
66+
}
67+
68+
/**
69+
* Type into the command palette
70+
*/
71+
async type(text: string): Promise<void> {
72+
await this.input.fill(text);
73+
}
74+
75+
/**
76+
* Execute a command by name with retry logic
77+
*/
78+
async execute(command: string, maxRetries = 3): Promise<void> {
79+
for (let attempt = 0; attempt < maxRetries; attempt++) {
80+
await this.open();
81+
await this.type(`> ${command}`);
82+
83+
// Wait for results to populate
84+
await this.firstResult.waitFor({ state: 'visible', timeout: MaxTimeout });
85+
await this.page.waitForTimeout(300); // Brief wait for results to settle
86+
87+
const firstResultText = await this.firstResult.textContent();
88+
if (firstResultText === command) {
89+
await this.input.press('Enter');
90+
// Wait for command palette to close completely
91+
await this.widget.waitFor({ state: 'hidden', timeout: MaxTimeout });
92+
// Give the command time to execute and UI to settle
93+
await this.page.waitForTimeout(500);
94+
return;
95+
}
96+
97+
// Close palette and retry
98+
await this.close();
99+
await this.page.waitForTimeout(200);
100+
}
101+
102+
throw new Error(`Command "${command}" not found in command palette after ${maxRetries} attempts`);
103+
}
104+
105+
/**
106+
* Quick open a file by name
107+
*/
108+
async quickOpen(filename: string): Promise<void> {
109+
await this.page.keyboard.press('Control+P');
110+
await this.input.waitFor({ state: 'visible', timeout: MaxTimeout });
111+
await this.type(filename);
112+
await this.firstResult.waitFor({ state: 'visible', timeout: MaxTimeout });
113+
await this.input.press('Enter');
114+
}
115+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import type { Locator, Page } from '@playwright/test';
2+
import { MaxTimeout } from '../../specs/baseTest';
3+
4+
/**
5+
* Component for VS Code Bottom Panel interactions.
6+
* The panel contains Problems, Output, Debug Console, Terminal, and custom views.
7+
*/
8+
export class Panel {
9+
private readonly container: Locator;
10+
11+
constructor(private readonly page: Page) {
12+
this.container = page.locator('[id="workbench.parts.panel"]');
13+
}
14+
15+
/**
16+
* Get the panel container
17+
*/
18+
get locator(): Locator {
19+
return this.container;
20+
}
21+
22+
/**
23+
* Get the tablist in the panel
24+
*/
25+
get tablist(): Locator {
26+
return this.container.getByRole('tablist', { name: 'Active View Switcher' });
27+
}
28+
29+
/**
30+
* Get a panel tab by name
31+
*/
32+
getTab(name: string | RegExp, exact = true): Locator {
33+
return this.container.getByRole('tab', { name: name, exact: exact });
34+
}
35+
36+
/**
37+
* Click a panel tab
38+
*/
39+
async clickTab(name: string | RegExp, exact = true): Promise<void> {
40+
await this.getTab(name, exact).click();
41+
}
42+
43+
/**
44+
* Check if a tab is selected
45+
*/
46+
async isTabSelected(name: string | RegExp, exact = true): Promise<boolean> {
47+
const tab = this.getTab(name, exact);
48+
const expanded = await tab.getAttribute('aria-expanded');
49+
const selected = await tab.getAttribute('aria-selected');
50+
return expanded === 'true' || selected === 'true';
51+
}
52+
53+
/**
54+
* Wait for a tab to appear
55+
*/
56+
async waitForTab(name: string | RegExp, exact = true, timeout = MaxTimeout): Promise<void> {
57+
await this.getTab(name, exact).waitFor({ state: 'visible', timeout: timeout });
58+
}
59+
60+
/**
61+
* Get a section button in the panel content area
62+
*/
63+
getSection(name: string | RegExp): Locator {
64+
return this.container.getByRole('button', { name: name });
65+
}
66+
67+
/**
68+
* Wait for a section to appear
69+
*/
70+
async waitForSection(name: string | RegExp, timeout = MaxTimeout): Promise<void> {
71+
await this.getSection(name).waitFor({ state: 'visible', timeout: timeout });
72+
}
73+
74+
/**
75+
* Check if a section is visible
76+
*/
77+
async isSectionVisible(name: string | RegExp): Promise<boolean> {
78+
return this.getSection(name).isVisible();
79+
}
80+
81+
// Common VS Code panel tabs
82+
get problemsTab(): Locator {
83+
return this.getTab(/Problems/);
84+
}
85+
86+
get outputTab(): Locator {
87+
return this.getTab(/Output/);
88+
}
89+
90+
get debugConsoleTab(): Locator {
91+
return this.getTab(/Debug Console/);
92+
}
93+
94+
get terminalTab(): Locator {
95+
return this.getTab(/Terminal/);
96+
}
97+
98+
/**
99+
* Open the panel if it's hidden
100+
*/
101+
async open(): Promise<void> {
102+
const isVisible = await this.container.isVisible();
103+
if (!isVisible) {
104+
await this.page.keyboard.press('Control+J');
105+
await this.container.waitFor({ state: 'visible', timeout: MaxTimeout });
106+
}
107+
}
108+
109+
/**
110+
* Close the panel
111+
*/
112+
async close(): Promise<void> {
113+
const isVisible = await this.container.isVisible();
114+
if (isVisible) {
115+
await this.page.keyboard.press('Control+J');
116+
await this.container.waitFor({ state: 'hidden', timeout: MaxTimeout });
117+
}
118+
}
119+
}

0 commit comments

Comments
 (0)