experiments in a post-browser web
1/**
2 * Browser Extension Popup Tests
3 *
4 * Tests for the command bar popup (popup.html/popup.js).
5 * Run with: BROWSER=chrome yarn test:extension:browser
6 */
7
8import { test, expect } from '@playwright/test';
9import { getSharedExtension, ExtensionApp } from '../fixtures/extension-app';
10import {
11 waitForCommandInput,
12 typeCommand,
13 tabComplete,
14 executeCommand,
15 getTypedText,
16 getDisplayText,
17 isPlaceholderShown,
18 getTypedPortion,
19} from '../helpers/extension-utils';
20import { Page } from '@playwright/test';
21
22let ext: ExtensionApp;
23let popup: Page;
24
25test.beforeAll(async () => {
26 ext = await getSharedExtension();
27});
28
29// Don't close shared extension - let Playwright handle cleanup
30// This allows multiple test files to share the same browser instance
31
32test.beforeEach(async () => {
33 // Open a fresh popup for each test
34 popup = await ext.openPopup();
35 await waitForCommandInput(popup);
36});
37
38test.afterEach(async () => {
39 // Close the popup page
40 if (popup && !popup.isClosed()) {
41 await popup.close();
42 }
43});
44
45test.describe('Popup Command Bar', () => {
46 test('shows placeholder text when empty', async () => {
47 const placeholder = await isPlaceholderShown(popup);
48 expect(placeholder).toBe(true);
49
50 const displayText = await getDisplayText(popup);
51 expect(displayText).toBe('tag, note, or search...');
52 });
53
54 test('hides placeholder when typing', async () => {
55 await typeCommand(popup, 't');
56
57 const placeholder = await isPlaceholderShown(popup);
58 expect(placeholder).toBe(false);
59 });
60
61 test('autocompletes "t" to show "tag" suggestion', async () => {
62 await typeCommand(popup, 't');
63
64 const displayText = await getDisplayText(popup);
65 // Should show typed 't' plus autocomplete suggestion 'ag'
66 expect(displayText).toBe('tag');
67
68 const typedPortion = await getTypedPortion(popup);
69 expect(typedPortion).toBe('t');
70 });
71
72 test('autocompletes "n" to show "note" suggestion', async () => {
73 await typeCommand(popup, 'n');
74
75 const displayText = await getDisplayText(popup);
76 expect(displayText).toBe('note');
77
78 const typedPortion = await getTypedPortion(popup);
79 expect(typedPortion).toBe('n');
80 });
81
82 test('autocompletes "s" to show "search" suggestion', async () => {
83 await typeCommand(popup, 's');
84
85 const displayText = await getDisplayText(popup);
86 expect(displayText).toBe('search');
87
88 const typedPortion = await getTypedPortion(popup);
89 expect(typedPortion).toBe('s');
90 });
91
92 test('Tab completes the command', async () => {
93 await typeCommand(popup, 't');
94 await tabComplete(popup);
95
96 const inputValue = await getTypedText(popup);
97 expect(inputValue).toBe('tag ');
98 });
99
100 test('Tab completes "no" to "note "', async () => {
101 await typeCommand(popup, 'no');
102 await tabComplete(popup);
103
104 const inputValue = await getTypedText(popup);
105 expect(inputValue).toBe('note ');
106 });
107
108 test('Tab completes "se" to "search "', async () => {
109 await typeCommand(popup, 'se');
110 await tabComplete(popup);
111
112 const inputValue = await getTypedText(popup);
113 expect(inputValue).toBe('search ');
114 });
115
116 test('Enter with partial command completes it', async () => {
117 await typeCommand(popup, 't');
118 await executeCommand(popup);
119
120 // Should autocomplete to "tag " since no args provided
121 const inputValue = await getTypedText(popup);
122 expect(inputValue).toBe('tag ');
123 });
124
125 test('no autocomplete for non-matching input', async () => {
126 await typeCommand(popup, 'x');
127
128 const displayText = await getDisplayText(popup);
129 // Should only show what was typed, no suggestion
130 expect(displayText).toBe('x');
131
132 const typedPortion = await getTypedPortion(popup);
133 expect(typedPortion).toBe('x');
134 });
135
136 test('preserves case in typed text display', async () => {
137 await typeCommand(popup, 'TAG');
138
139 // Typed text should be preserved as-is in display
140 const typedPortion = await getTypedPortion(popup);
141 expect(typedPortion).toBe('TAG');
142
143 // But autocomplete still shows (matching is case-insensitive)
144 const displayText = await getDisplayText(popup);
145 // Display shows typed portion + rest of suggestion
146 expect(displayText).toContain('TAG');
147 });
148
149 test('no suggestion shown after space (args mode)', async () => {
150 await typeCommand(popup, 'tag ');
151
152 // After space, should just show what's typed
153 const displayText = await getDisplayText(popup);
154 expect(displayText).toBe('tag ');
155 });
156
157 test('shows typed text with args', async () => {
158 await typeCommand(popup, 'tag foo, bar');
159
160 const displayText = await getDisplayText(popup);
161 expect(displayText).toBe('tag foo, bar');
162
163 const typedPortion = await getTypedPortion(popup);
164 expect(typedPortion).toBe('tag foo, bar');
165 });
166
167 test('input is focused on popup open', async () => {
168 const isFocused = await popup.evaluate(() => {
169 return document.activeElement?.id === 'command-input';
170 });
171 expect(isFocused).toBe(true);
172 });
173
174 test('input accepts text input', async () => {
175 // Type using keyboard instead of fill
176 await popup.keyboard.type('tag test');
177
178 const inputValue = await getTypedText(popup);
179 expect(inputValue).toBe('tag test');
180 });
181});