···6565 onClickBackButton: () => window.router.go("/settings"),
6666 })}
6767 <main>
6868- <section class="settings-section">
6868+ <section
6969+ class="settings-section"
7070+ data-testid="settings-section-color-scheme"
7171+ >
6972 <h2>Color scheme</h2>
7073 <p>Choose between light and dark mode.</p>
7174 <select
···9598 </option>
9699 </select>
97100 </section>
9898- <section class="settings-section">
101101+ <section
102102+ class="settings-section"
103103+ data-testid="settings-section-highlight-color"
104104+ >
99105 <h2>Highlight color</h2>
100106 <p>Choose the highlight color for buttons and links.</p>
101107 <div class="settings-color-picker">
···116122 </button>
117123 </div>
118124 </section>
119119- <section class="settings-section">
125125+ <section
126126+ class="settings-section"
127127+ data-testid="settings-section-like-color"
128128+ >
120129 <h2>Like color</h2>
121130 <p>Choose the color for liked posts.</p>
122131 <div class="settings-color-picker">
+4-1
src/js/views/settings/blockedAccounts.view.js
···9595 onClickBackButton: () => window.router.go("/settings"),
9696 })}
9797 <main>
9898- <p class="blocked-account-description">
9898+ <p
9999+ class="blocked-account-description"
100100+ data-testid="page-description"
101101+ >
99102 Blocked accounts cannot reply to your posts, mention you, or
100103 interact with you. You won't see their content.
101104 </p>
+4-1
src/js/views/settings/mutedAccounts.view.js
···9393 onClickBackButton: () => window.router.go("/settings"),
9494 })}
9595 <main>
9696- <p class="muted-account-description">
9696+ <p
9797+ class="muted-account-description"
9898+ data-testid="page-description"
9999+ >
97100 Muted accounts have their posts removed from your feed and
98101 from your notifications. Mutes are completely private.
99102 </p>
+6-2
src/js/views/settings/mutedWords.view.js
···263263 onClickBackButton: () => window.router.go("/settings"),
264264 })}
265265 <main>
266266- <form class="muted-word-form" @submit=${(e) => handleSubmit(e)}>
266266+ <form
267267+ class="muted-word-form"
268268+ data-testid="muted-word-form"
269269+ @submit=${(e) => handleSubmit(e)}
270270+ >
267271 <h2>Add muted words and tags</h2>
268268- <p>
272272+ <p data-testid="page-description">
269273 Posts can be muted based on their text, their tags, or both.
270274 </p>
271275 <input
···43434444 // Open the post action menu and click "Delete post"
4545 await feedItem.locator(".text-button").click();
4646- await page.locator("context-menu-item", { hasText: "Delete post" }).click();
4646+ await page.locator('[data-testid="menu-action-post-delete"]').click();
47474848 // Confirm deletion if a confirmation dialog appears
4949 const confirmButton = page.locator("button.confirm-button");
···89899090 // Open the post action menu and click "Delete post"
9191 await largePost.locator(".text-button").click();
9292- await page.locator("context-menu-item", { hasText: "Delete post" }).click();
9292+ await page.locator('[data-testid="menu-action-post-delete"]').click();
93939494 // Confirm deletion if a confirmation dialog appears
9595 const confirmButton = page.locator("button.confirm-button");
···147147 const feedItem = profileView.locator('[data-testid="feed-item"]');
148148 await expect(feedItem).toHaveCount(1, { timeout: 10000 });
149149 await feedItem.locator(".text-button").click();
150150- await page.locator("context-menu-item", { hasText: "Delete post" }).click();
150150+ await page.locator('[data-testid="menu-action-post-delete"]').click();
151151 const confirmButton = page.locator("button.confirm-button");
152152 await expect(confirmButton).toBeVisible({ timeout: 5000 });
153153 await confirmButton.click();
+10-16
tests/e2e/specs/flows/editProfile.test.js
···6060 await dialog.locator('[data-testid="edit-profile-save-button"]').click();
61616262 // Verify the toast shows success
6363- await expect(page.locator(".toast")).toContainText("Profile updated", {
6363+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
6464 timeout: 5000,
6565 });
6666···233233 // Context menu should appear with "Upload from Files" and "Remove Avatar"
234234 const menu = dialog.locator(".edit-profile-avatar-menu");
235235 await expect(
236236- menu.locator("context-menu-item", { hasText: "Upload from Files" }),
236236+ menu.locator('[data-testid="menu-action-avatar-upload"]'),
237237 ).toBeVisible({ timeout: 5000 });
238238 await expect(
239239- menu.locator("context-menu-item", { hasText: "Remove Avatar" }),
239239+ menu.locator('[data-testid="menu-action-avatar-remove"]'),
240240 ).toBeVisible();
241241 });
242242···274274 // Context menu should only have "Upload from Files"
275275 const menu = dialog.locator(".edit-profile-avatar-menu");
276276 await expect(
277277- menu.locator("context-menu-item", { hasText: "Upload from Files" }),
277277+ menu.locator('[data-testid="menu-action-avatar-upload"]'),
278278 ).toBeVisible({ timeout: 5000 });
279279 await expect(
280280- menu.locator("context-menu-item", { hasText: "Remove Avatar" }),
280280+ menu.locator('[data-testid="menu-action-avatar-remove"]'),
281281 ).toHaveCount(0);
282282 });
283283···320320321321 // Click "Remove Avatar"
322322 const menu = dialog.locator(".edit-profile-avatar-menu");
323323- await menu
324324- .locator("context-menu-item", { hasText: "Remove Avatar" })
325325- .click();
323323+ await menu.locator('[data-testid="menu-action-avatar-remove"]').click();
326324327325 // Avatar image should be removed from the preview (placeholder may still exist)
328326 await expect(
···334332 // Save
335333 await dialog.locator('[data-testid="edit-profile-save-button"]').click();
336334337337- await expect(page.locator(".toast")).toContainText("Profile updated", {
335335+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
338336 timeout: 5000,
339337 });
340338 });
···376374377375 // Click "Remove Banner"
378376 const menu = dialog.locator(".edit-profile-banner-menu");
379379- await menu
380380- .locator("context-menu-item", { hasText: "Remove Banner" })
381381- .click();
377377+ await menu.locator('[data-testid="menu-action-banner-remove"]').click();
382378383379 // Banner image should be removed from the preview
384380 await expect(
···388384 // Save
389385 await dialog.locator('[data-testid="edit-profile-save-button"]').click();
390386391391- await expect(page.locator(".toast")).toContainText("Profile updated", {
387387+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
392388 timeout: 5000,
393389 });
394390 });
···431427 // Set a file on the hidden file input to trigger the cropper
432428 const [fileChooser] = await Promise.all([
433429 page.waitForEvent("filechooser"),
434434- menu
435435- .locator("context-menu-item", { hasText: "Upload from Files" })
436436- .click(),
430430+ menu.locator('[data-testid="menu-action-avatar-upload"]').click(),
437431 ]);
438432439433 await fileChooser.setFiles({
+9-5
tests/e2e/specs/flows/followUser.test.js
···3636 // Click the follow button
3737 await profileView.locator('[data-testid="follow-button"]').click();
38383939- // Verify the button changes to "Following"
3939+ // Verify the button changes to following state
4040 await expect(
4141- profileView.locator('[data-testid="follow-button"]'),
4242- ).toContainText("Following", { timeout: 10000 });
4141+ profileView.locator(
4242+ '[data-testid="follow-button"][data-teststate="following"]',
4343+ ),
4444+ ).toBeVisible({ timeout: 10000 });
43454446 // Verify the follower count incremented
4547 await expect(
···91939294 const profileView = page.locator("#profile-view");
9395 await expect(
9494- profileView.locator('[data-testid="follow-button"]'),
9595- ).toContainText("Following", { timeout: 10000 });
9696+ profileView.locator(
9797+ '[data-testid="follow-button"][data-teststate="following"]',
9898+ ),
9999+ ).toBeVisible({ timeout: 10000 });
9610097101 // Click the follow button to unfollow
98102 await profileView.locator('[data-testid="follow-button"]').click();
+3-7
tests/e2e/specs/flows/hidePost.test.js
···34343535 // Open the post's context menu and click "Hide post for me"
3636 await feedItem.locator(".text-button").click();
3737- await page
3838- .locator("context-menu-item", { hasText: "Hide post for me" })
3939- .click();
3737+ await page.locator('[data-testid="menu-action-post-hide"]').click();
40384139 // Confirm the hide action in the dialog
4240 const confirmButton = page.locator("button.confirm-button");
···4442 await confirmButton.click();
45434644 // The post should immediately be hidden from the feed
4747- await expect(page.locator(".toast")).toContainText("Post hidden", {
4545+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
4846 timeout: 5000,
4947 });
5048 await expect(feedItem).toHaveCount(0, { timeout: 10000 });
···106104 // Open the reply's context menu and click "Hide reply for me"
107105 const replyPost = view.locator('[data-testid="small-post"]');
108106 await replyPost.locator(".text-button").click();
109109- await page
110110- .locator("context-menu-item", { hasText: "Hide reply for me" })
111111- .click();
107107+ await page.locator('[data-testid="menu-action-post-hide"]').click();
112108113109 // Confirm the hide action in the dialog
114110 const confirmButton = page.locator("button.confirm-button");
+26-20
tests/e2e/specs/flows/labelPreference.test.js
···176176 profileView.locator('[data-testid="profile-name"]'),
177177 ).toContainText("Test Labeler", { timeout: 10000 });
178178179179- // Should show "+ Subscribe" button
179179+ // Should show not-subscribed state
180180 await expect(
181181- profileView.locator('[data-testid="subscribe-button"]'),
182182- ).toContainText("+ Subscribe");
181181+ profileView.locator(
182182+ '[data-testid="subscribe-button"][data-teststate="not-subscribed"]',
183183+ ),
184184+ ).toBeVisible();
183185184186 // Label preference buttons should NOT be visible (not subscribed)
185187 await expect(
···189191 // Click Subscribe
190192 await profileView.locator('[data-testid="subscribe-button"]').click();
191193192192- // Button should change to "Subscribed"
194194+ // Button should change to subscribed state
193195 await expect(
194194- profileView.locator('[data-testid="subscribe-button"]'),
195195- ).toContainText("Subscribed", { timeout: 10000 });
196196+ profileView.locator(
197197+ '[data-testid="subscribe-button"][data-teststate="subscribed"]',
198198+ ),
199199+ ).toBeVisible({ timeout: 10000 });
196200197201 // Toast should confirm subscription
198198- await expect(page.locator(".toast")).toContainText(
199199- "Subscribed to labeler",
200200- { timeout: 5000 },
201201- );
202202+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
203203+ timeout: 5000,
204204+ });
202205203206 // Label preference buttons should now be visible
204207 await expect(
···241244 profileView.locator('[data-testid="profile-name"]'),
242245 ).toContainText("Test Labeler", { timeout: 10000 });
243246244244- // Should show "Subscribed" button
247247+ // Should show subscribed state
245248 await expect(
246246- profileView.locator('[data-testid="subscribe-button"]'),
247247- ).toContainText("Subscribed");
249249+ profileView.locator(
250250+ '[data-testid="subscribe-button"][data-teststate="subscribed"]',
251251+ ),
252252+ ).toBeVisible();
248253249254 // Label preference buttons should be visible (subscribed)
250255 await expect(
···254259 // Click "Subscribed" to unsubscribe
255260 await profileView.locator('[data-testid="subscribe-button"]').click();
256261257257- // Button should change to "+ Subscribe"
262262+ // Button should change to not-subscribed state
258263 await expect(
259259- profileView.locator('[data-testid="subscribe-button"]'),
260260- ).toContainText("+ Subscribe", { timeout: 10000 });
264264+ profileView.locator(
265265+ '[data-testid="subscribe-button"][data-teststate="not-subscribed"]',
266266+ ),
267267+ ).toBeVisible({ timeout: 10000 });
261268262269 // Toast should confirm unsubscription
263263- await expect(page.locator(".toast")).toContainText(
264264- "Unsubscribed from labeler",
265265- { timeout: 5000 },
266266- );
270270+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
271271+ timeout: 5000,
272272+ });
267273268274 // Label preference buttons should no longer be visible
269275 await expect(
···4444 ).toContainText("Other User", { timeout: 10000 });
45454646 await profileView.locator(".ellipsis-button").click();
4747- await page
4848- .locator("context-menu-item", { hasText: "Mute Account" })
4949- .click();
4747+ await page.locator('[data-testid="menu-action-profile-mute"]').click();
50485151- // Verify context menu now shows "Unmute Account"
4949+ // Verify context menu now shows muted state
5250 await profileView.locator(".ellipsis-button").click();
5351 await expect(
5454- page.locator("context-menu-item", { hasText: "Unmute Account" }),
5252+ page.locator(
5353+ '[data-testid="menu-action-profile-mute"][data-teststate="muted"]',
5454+ ),
5555 ).toBeVisible({ timeout: 5000 });
5656 // Close the menu by pressing Escape
5757 await page.keyboard.press("Escape");
···96969797 // Open the post's context menu and mute the user
9898 await feedItem.locator(".text-button").click();
9999- await page
100100- .locator("context-menu-item", { hasText: "Mute Account" })
101101- .click();
9999+ await page.locator('[data-testid="menu-action-post-mute"]').click();
102100103101 // Verify a toast confirms the mute action
104104- await expect(page.locator(".toast")).toContainText("Account muted", {
102102+ await expect(page.locator('[data-testid="toast"]')).toBeVisible({
105103 timeout: 5000,
106104 });
107105···117115 profileView.locator('[data-testid="profile-name"]'),
118116 ).toContainText("Other User", { timeout: 10000 });
119117120120- // Verify context menu shows "Unmute Account"
118118+ // Verify context menu shows muted state
121119 await profileView.locator(".ellipsis-button").click();
122120 await expect(
123123- page.locator("context-menu-item", { hasText: "Unmute Account" }),
121121+ page.locator(
122122+ '[data-testid="menu-action-profile-mute"][data-teststate="muted"]',
123123+ ),
124124 ).toBeVisible({ timeout: 5000 });
125125 });
126126···162162 ).toContainText("Other User", { timeout: 10000 });
163163164164 await profileView.locator(".ellipsis-button").click();
165165- await page
166166- .locator("context-menu-item", { hasText: "Unmute Account" })
167167- .click();
165165+ await page.locator('[data-testid="menu-action-profile-mute"]').click();
168166169167 // Navigate to home and verify posts reappear
170168 await page.goto("/");
+7-7
tests/e2e/specs/flows/pinPost.test.js
···3939 0,
4040 );
41414242- // Open the post action menu and click "Pin to your profile"
4242+ // Open the post action menu and click the pin item
4343 await feedItem.locator(".text-button").click();
4444- await page
4545- .locator("context-menu-item", { hasText: "Pin to your profile" })
4646- .click();
4444+ await page.locator('[data-testid="menu-action-post-pin"]').click();
47454846 // The pinned label should appear optimistically
4947 await expect(
···5856 cid: post.cid,
5957 });
60586161- // Re-opening the menu now shows "Unpin from your profile"
5959+ // Re-opening the menu now shows pinned state
6260 await profileView
6361 .locator('[data-testid="feed-item"]')
6462 .first()
6563 .locator(".text-button")
6664 .click();
6765 await expect(
6868- page.locator("context-menu-item", { hasText: "Unpin from your profile" }),
6666+ page.locator(
6767+ '[data-testid="menu-action-post-pin"][data-teststate="pinned"]',
6868+ ),
6969 ).toBeVisible();
7070 });
7171···9393 await feedItem.locator(".text-button").click();
94949595 await expect(
9696- page.locator("context-menu-item", { hasText: "Pin to your profile" }),
9696+ page.locator('[data-testid="menu-action-post-pin"]'),
9797 ).toHaveCount(0);
9898 });
9999});