refactor: organize E2E tests with page objects

This commit is contained in:
2026-10-07 21:13:40 +09:00
parent 1e03e8f4ff
commit 48ef396ec1
28 changed files with 1494 additions and 834 deletions
+44
View File
@@ -0,0 +1,44 @@
import type { Locator, Page } from "@playwright/test";
export class AuthPage {
readonly name: Locator;
readonly email: Locator;
readonly password: Locator;
readonly confirmPassword: Locator;
readonly setupCode: Locator;
readonly signInButton: Locator;
readonly createAccountButton: Locator;
readonly welcome: Locator;
readonly error: Locator;
constructor(readonly page: Page) {
this.name = page.getByLabel("Your name", { exact: true });
this.email = page.getByLabel("Email address");
this.password = page.getByLabel("Password", { exact: true });
this.confirmPassword = page.getByLabel("Confirm password", { exact: true });
this.setupCode = page.getByLabel("Setup code");
this.signInButton = page.getByRole("button", { name: "Sign in", exact: true });
this.createAccountButton = page.getByRole("button", { name: "Create account", exact: true });
this.welcome = page.getByRole("heading", { name: "Welcome back." });
this.error = page.getByRole("alert");
}
async goTo(mode: "login" | "setup" = "login") {
await this.page.goto(`/${mode}`, { waitUntil: "networkidle" });
}
async signIn(email: string, password: string) {
await this.email.fill(email);
await this.password.fill(password);
await this.signInButton.click();
}
async createAccount(input: { name: string; email: string; password: string; setupCode: string }) {
await this.name.fill(input.name);
await this.email.fill(input.email);
await this.password.fill(input.password);
await this.confirmPassword.fill(input.password);
await this.setupCode.fill(input.setupCode);
await this.createAccountButton.click();
}
}
+26
View File
@@ -0,0 +1,26 @@
import type { Locator, Page } from "@playwright/test";
import { WorkspacePage } from "./WorkspacePage";
export class ConnectedAccountsDialog {
readonly dialog: Locator;
readonly unconfiguredMastodon: Locator;
readonly authorizeMastodon: Locator;
readonly signOut: Locator;
constructor(readonly page: Page) {
this.dialog = page.getByRole("dialog", { name: "Connected accounts" });
this.unconfiguredMastodon = this.dialog.getByText(
"No Mastodon servers have been configured yet.",
);
this.authorizeMastodon = this.dialog.getByRole("button", { name: "Authorize with Mastodon" });
this.signOut = this.dialog.getByRole("button", { name: "Sign out", exact: true });
}
async open() {
await new WorkspacePage(this.page).openConnectedAccounts();
}
async close() {
await this.page.keyboard.press("Escape");
}
}
+140
View File
@@ -0,0 +1,140 @@
import { expect, type Locator, type Page } from "@playwright/test";
import { WorkspacePage } from "./WorkspacePage";
export class DeckPage {
get dialogs() {
return this.page.getByRole("dialog");
}
readonly saveAsDeck: Locator;
readonly savedNotice: Locator;
readonly rename: Locator;
readonly deckName: Locator;
readonly saveName: Locator;
readonly createDeck: Locator;
readonly newDeckName: Locator;
readonly confirmCreate: Locator;
readonly deckProfile: Locator;
readonly heading: Locator;
readonly deleteDeck: Locator;
readonly confirmDelete: Locator;
readonly undo: Locator;
readonly columnNavigation: Locator;
readonly addColumnButton: Locator;
readonly columnDialog: Locator;
readonly columnName: Locator;
readonly editorProfile: Locator;
readonly columnType: Locator;
readonly primaryList: Locator;
readonly alternateList: Locator;
readonly listId: Locator;
readonly addAndSearch: Locator;
readonly saveChanges: Locator;
readonly editColumn: Locator;
readonly alert: Locator;
readonly reloadLatest: Locator;
readonly importLegacy: Locator;
readonly createDialog: Locator;
readonly newDeckTitleInput: Locator;
readonly columns: Locator;
readonly profileOptions: Locator;
readonly columnTitles: Locator;
readonly board: Locator;
constructor(readonly page: Page) {
this.saveAsDeck = this.page.getByRole("button", { name: "Save as deck", exact: true });
this.savedNotice = this.page.getByText("Saved to server · Shared across devices");
this.rename = this.page.getByRole("button", { name: "Rename" });
this.deckName = this.page.getByLabel("Research topic / deck name");
this.saveName = this.page.getByRole("button", { name: "Save name" });
this.createDeck = this.page.getByRole("button", { name: "Create deck", exact: true });
this.newDeckName = this.page.getByLabel("New deck name");
this.confirmCreate = this.page.getByRole("button", { name: "Create", exact: true });
this.deckProfile = this.page.getByLabel("Deck profile", { exact: true });
this.profileOptions = this.deckProfile.locator("option");
this.heading = this.page.getByRole("heading", { level: 1 });
this.deleteDeck = this.page.getByRole("button", { name: "Delete deck", exact: true });
this.confirmDelete = this.page.getByRole("button", { name: "Delete", exact: true });
this.undo = this.page.getByRole("button", { name: "Undo" });
this.columnNavigation = this.page.getByRole("navigation", { name: "Columns" });
this.addColumnButton = this.page.getByRole("button", { name: "Add column", exact: true });
this.columnDialog = this.page.getByRole("dialog", { name: "Add column", exact: true });
this.columnName = this.page.getByLabel("Column name", { exact: true });
this.editorProfile = this.page.getByRole("combobox", {
name: "Connection profile",
exact: true,
});
this.columnType = this.page.getByRole("combobox", { name: "Column type", exact: true });
this.primaryList = this.page.getByRole("button", { name: "My E2E List", exact: true });
this.alternateList = this.page.getByRole("button", { name: "Alternate List", exact: true });
this.listId = this.page.getByLabel("List", { exact: true });
this.addAndSearch = this.page.getByRole("button", { name: "Add and search" });
this.saveChanges = this.page.getByRole("button", { name: "Save changes" });
this.editColumn = this.page.getByRole("menuitem", { name: "Edit", exact: true });
this.alert = this.page.getByRole("alert");
this.reloadLatest = this.page.getByRole("button", { name: "Reload latest deck" });
this.importLegacy = this.page.getByRole("button", { name: "Import legacy decks" });
this.createDialog = this.page.getByRole("dialog", { name: "Create deck", exact: true });
this.newDeckTitleInput = this.page.locator("#new-deck-title");
this.columns = this.page.locator("[data-testid='deck-column']");
this.columnTitles = this.page.locator("[data-testid='deck-column'] h2");
this.board = this.page.locator("[data-testid='deck-board']");
}
column(title: string) {
return this.page.getByRole("region", { name: title, exact: true });
}
columnPosts(title: string) {
return this.column(title).locator("article");
}
columnPostText(title: string, text: string, exact = false) {
return this.column(title).getByText(text, { exact });
}
columnError(title: string) {
return this.column(title).getByRole("alert");
}
async loadMore(title: string) {
await this.column(title).getByRole("button", { name: "Load more" }).click();
}
async retryColumn(title: string) {
await this.column(title).getByRole("button", { name: "Retry" }).click();
}
async openColumnActions(title: string) {
await this.column(title)
.getByRole("button", { name: `Actions for ${title}` })
.click();
}
columnHeading(title: string) {
return this.columns.getByRole("heading", { name: title, exact: true });
}
columnAction(name: string) {
return this.page.getByRole("menuitem", { name, exact: true });
}
post(url: string) {
return this.page.locator(`[data-testid="research-post"][data-post-url="${url}"]`);
}
postText(text: string) {
return this.page.getByText(text, { exact: true });
}
async goTo(search = "") {
await this.page.goto(`/deck${search}`);
}
async addColumn(title: string, query: string, profile = "e2e", kind = "search") {
const workspace = new WorkspacePage(this.page);
await workspace.showDeckManagement();
await this.addColumnButton.click();
const editor = this.page.getByRole("form", { name: "Add column", exact: true });
await editor.getByLabel("Column name", { exact: true }).fill(title);
await editor
.getByRole("combobox", { name: "Connection profile", exact: true })
.selectOption({ label: profile });
await editor.getByRole("combobox", { name: "Column type", exact: true }).selectOption(kind);
await editor
.getByLabel(kind === "search" ? "Twitter search query" : kind === "user" ? "User" : "List", {
exact: true,
})
.fill(query);
await editor.getByRole("button", { name: "Add and search" }).click();
await workspace.closeMobileSidebar();
await expect(this.column(title).locator("article").first()).toBeVisible();
}
}
+67
View File
@@ -0,0 +1,67 @@
import type { Locator, Page } from "@playwright/test";
export class HomePage {
readonly page: Page;
readonly input: Locator;
readonly previewAction: Locator;
readonly applyAction: Locator;
readonly morningRoutine: Locator;
readonly finishRoutine: Locator;
readonly recordWeight: Locator;
readonly weight: Locator;
readonly saveMeasurement: Locator;
readonly todayPlan: Locator;
readonly dailyBrief: Locator;
readonly flowTitles: Locator;
constructor(page: Page) {
this.page = page;
this.input = page.getByRole("textbox", { name: "Home input" });
this.previewAction = page.getByRole("button", { name: "Preview action" });
this.applyAction = page.getByRole("button", { name: "Apply", exact: true });
this.morningRoutine = page.getByRole("button", { name: /Morning routine 0 of 3/ });
this.finishRoutine = page.getByRole("button", { name: "Finish routine" });
this.recordWeight = page.getByRole("button", { name: /Record weight A quick check-in/ });
this.weight = page.getByRole("spinbutton", { name: "Weight", exact: true });
this.saveMeasurement = page.getByRole("button", { name: "Save measurement" });
this.todayPlan = page.getByRole("region", { name: "Today’s plan" });
this.dailyBrief = page.getByRole("region", { name: "Daily brief" });
this.flowTitles = page.getByTestId("execution-flow-title");
}
recordedWeight(weight: string) {
return this.page.getByRole("button", { name: `Record weight Recorded · ${weight} kg` });
}
greeting(name: string) {
return this.page.getByRole("heading", { name: `Good morning, ${name}` });
}
async goTo(search = "") {
await this.page.goto(`/${search}`);
}
routineStep(name: string | RegExp) {
return this.page.getByRole("checkbox", { name });
}
completeTask(name: string) {
return this.page.getByRole("button", { name: `Complete ${name}`, exact: true });
}
restoreTask(name: string) {
return this.page.getByRole("button", { name: `Restore ${name}`, exact: true });
}
completedTask(name: string) {
return this.page.getByRole("button", { name: `${name} Completed`, exact: true });
}
conversation(name: string) {
return this.page.getByRole("link", { name: `Open conversation with ${name}` });
}
async openBriefConversation(name: string) {
await this.dailyBrief.getByRole("button", { name: `Reply to ${name}` }).click();
}
async previewCommand(command: string) {
await this.input.fill(command);
await this.previewAction.click();
}
async recordMeasurement(weight: string) {
await this.recordWeight.click();
await this.weight.fill(weight);
await this.saveMeasurement.click();
}
}
+44
View File
@@ -0,0 +1,44 @@
import type { Locator, Page } from "@playwright/test";
export class JournalPage {
readonly page: Page;
readonly saveEntry: Locator;
readonly entryText: Locator;
readonly noteEntries: Locator;
readonly weightEntries: Locator;
readonly weightTab: Locator;
readonly weight: Locator;
readonly source: Locator;
readonly correctionDialog: Locator;
readonly summarizeEntries: Locator;
readonly summaryDialog: Locator;
readonly summary: Locator;
constructor(page: Page) {
this.page = page;
this.saveEntry = page.getByRole("button", { name: "Save entry", exact: true });
this.entryText = page.getByRole("textbox", { name: "Entry text" });
this.noteEntries = page.getByRole("article", { name: "Note entry" });
this.weightEntries = page.getByRole("article", { name: "Weight entry" });
this.weightTab = page.getByRole("button", { name: "Weight", exact: true });
this.weight = page.getByRole("spinbutton", { name: "Weight", exact: true });
this.source = page.getByRole("textbox", { name: "Source" });
this.correctionDialog = page.getByRole("dialog", { name: "Correct entry" });
this.summarizeEntries = page.getByRole("button", { name: "Summarize entries", exact: true });
this.summaryDialog = page.getByRole("dialog", { name: "Summarize entries" });
this.summary = this.summaryDialog.getByRole("textbox", { name: "Entry summary" });
}
async goTo(search = "") {
await this.page.goto(`/journal${search}`);
}
async correctWeight(weight: string) {
await this.weightEntries.getByRole("button", { name: "Correct" }).click();
await this.correctionDialog
.getByRole("spinbutton", { name: "Weight", exact: true })
.fill(weight);
await this.correctionDialog.getByRole("button", { name: "Add correction" }).click();
}
async excludeFromSummary(name: string | RegExp) {
await this.summaryDialog.getByRole("checkbox", { name }).uncheck();
}
}
+72
View File
@@ -0,0 +1,72 @@
import type { Locator, Page } from "@playwright/test";
export class MessagesPage {
readonly page: Page;
readonly searchInput: Locator;
readonly toolbarName: Locator;
readonly workspace: Locator;
readonly rows: Locator;
readonly reply: Locator;
readonly suggestions: Locator;
readonly scheduling: Locator;
readonly suggestedTimes: Locator;
readonly reviewReply: Locator;
readonly reviewDialog: Locator;
readonly trySending: Locator;
readonly editReply: Locator;
readonly leaveForLater: Locator;
readonly moveToInbox: Locator;
readonly backToMessages: Locator;
readonly inbox: Locator;
readonly outcome: Locator;
readonly dialogs: Locator;
constructor(page: Page) {
this.page = page;
this.searchInput = page.getByRole("textbox", { name: "Search messages" });
this.toolbarName = page.getByTestId("support-toolbar").locator("strong");
this.workspace = page.getByTestId("support-workspace");
this.rows = page.getByTestId("support-row");
this.reply = page.getByRole("textbox", { name: "Reply", exact: true });
this.suggestions = page.locator("[data-testid='conversation-suggestions'] summary:visible");
this.scheduling = page.locator("[data-testid='conversation-scheduling'] summary:visible");
this.suggestedTimes = page.getByRole("combobox", { name: "Suggested times" });
this.reviewReply = page.getByRole("button", { name: "Review reply", exact: true });
this.reviewDialog = page.getByRole("dialog", { name: "Review reply", exact: true });
this.trySending = this.reviewDialog.getByRole("button", { name: "Try sending" });
this.editReply = this.reviewDialog.getByRole("button", { name: "Back", exact: true });
this.leaveForLater = page.getByRole("button", { name: "Leave for later" });
this.moveToInbox = page.getByRole("button", { name: "Move to Inbox" });
this.backToMessages = page.getByRole("button", { name: "Back to messages" });
this.inbox = page.getByRole("button", { name: "Inbox", exact: true });
this.outcome = page.getByTestId("support-outcome");
this.dialogs = page.getByRole("dialog");
}
rowName(index: number) {
return this.rows.nth(index).locator("strong");
}
filter(view: "Inbox" | "Archive" | "Set aside" | "All" | "Awaiting reply") {
return this.page.getByRole("button", { name: view, exact: true });
}
async goTo(search = "") {
await this.page.goto(`/support${search}`);
}
conversation(subject: string) {
return this.rows.filter({ hasText: subject });
}
async chooseReplySuggestion(tone: string | RegExp) {
await this.suggestions.click();
await this.page.getByRole("button", { name: tone }).click();
}
async addSuggestedTime(slot: string) {
await this.scheduling.click();
await this.suggestedTimes.selectOption(slot);
await this.page.getByRole("button", { name: "Add to reply" }).click();
}
async selectView(view: "Inbox" | "Archive" | "Set aside" | "All" | "Awaiting reply") {
await this.filter(view).click();
}
async showConversationList() {
if (await this.backToMessages.isVisible()) await this.backToMessages.click();
}
}
+23
View File
@@ -0,0 +1,23 @@
import type { Locator, Page } from "@playwright/test";
export class OnboardingPage {
readonly name: Locator;
readonly continueButton: Locator;
readonly openWorkspace: Locator;
constructor(readonly page: Page) {
this.name = page.getByLabel("What should we call you?");
this.continueButton = page.getByRole("button", { name: "Continue", exact: true });
this.openWorkspace = page.getByRole("button", { name: "Open workspace" });
}
async goTo() {
await this.page.goto("/onboarding", { waitUntil: "networkidle" });
}
async complete() {
await this.page.waitForLoadState("networkidle");
await this.continueButton.click();
await this.openWorkspace.click();
}
}
+71
View File
@@ -0,0 +1,71 @@
import type { Locator, Page } from "@playwright/test";
export class ReaderPage {
readonly rows: Locator;
readonly searchInput: Locator;
readonly articleTitle: Locator;
readonly workspace: Locator;
readonly emptyState: Locator;
readonly forYouLabel: Locator;
readonly mockLabel: Locator;
readonly articleExplanation: Locator;
readonly legacyFilters: Locator;
readonly editInterestsButton: Locator;
readonly backButton: Locator;
readonly clearSearchButton: Locator;
constructor(readonly page: Page) {
this.rows = page.getByTestId("inbox-article-row");
this.searchInput = page.getByRole("textbox", { name: "Search articles" });
this.articleTitle = page.getByTestId("inbox-article-title");
this.workspace = page.getByTestId("inbox-workspace");
this.emptyState = page.getByText("No articles found", { exact: true });
this.forYouLabel = page.getByText("FOR YOU", { exact: true });
this.mockLabel = page.getByText("Mock", { exact: true });
this.articleExplanation = page.getByText("Why this article", { exact: true });
this.legacyFilters = page.getByRole("button", { name: /For you|Read later|Unread only/ });
this.editInterestsButton = page.getByRole("button", { name: "Edit interests", exact: true });
this.backButton = page.getByRole("button", { name: "Back to articles" });
this.clearSearchButton = page.getByRole("button", { name: "Clear search" });
}
async goTo(search = "") {
await this.page.goto(`/inbox${search}`, { waitUntil: "networkidle" });
}
article(title: string) {
return this.page.getByRole("article", { name: title, exact: true });
}
articleHeading(title: string) {
return this.article(title).getByRole("heading", { name: title, exact: true });
}
articleText(title: string, text: string) {
return this.article(title).getByText(text);
}
rowTitle(index: number) {
return this.rows.nth(index).getByTestId("inbox-row-title");
}
async selectArticle(title: string) {
await this.rows.filter({ hasText: title }).click();
}
async openFirstArticle() {
await this.rows.first().click();
}
async backToArticles() {
if (await this.backButton.isVisible()) await this.backButton.click();
}
async search(query: string) {
await this.searchInput.fill(query);
}
async clearSearch() {
await this.clearSearchButton.click();
}
}
+45
View File
@@ -0,0 +1,45 @@
import type { Locator, Page } from "@playwright/test";
export class ResearchPage {
readonly panel: Locator;
readonly conversation: Locator;
readonly composer: Locator;
readonly send: Locator;
readonly newChat: Locator;
readonly unconfiguredNotice: Locator;
readonly bubbles: Locator;
readonly messageLists: Locator;
readonly messageTables: Locator;
constructor(readonly page: Page) {
this.panel = this.page.getByRole("complementary", { name: "Research chat" });
this.conversation = this.page.getByRole("log", { name: "Research conversation" });
this.composer = this.panel.getByRole("textbox", { name: "Message", exact: true });
this.send = this.panel.getByRole("button", { name: "Send", exact: true });
this.newChat = this.page.getByRole("button", { name: "New chat", exact: true });
this.unconfiguredNotice = this.panel.getByText(
"The connection to Codex on your home server is not configured.",
);
this.bubbles = this.conversation.locator('[data-slot="bubble-content"]');
this.messageLists = this.conversation.locator("ul");
this.messageTables = this.conversation.getByRole("table");
}
async goTo(search = "", options?: Parameters<Page["goto"]>[1]) {
await this.page.goto(`/deck${search}`, options);
}
accountPicker(count: number) {
return this.page.getByRole("button", { name: `Connected accounts · ${count}` });
}
account(name: string) {
return this.page.getByRole("checkbox", { name, exact: true });
}
messageHeading(name: string) {
return this.conversation.getByRole("heading", { name, exact: true });
}
emphasis(text: string) {
return this.conversation.locator("strong").filter({ hasText: text });
}
citation(name: string) {
return this.conversation.getByRole("link", { name, exact: true });
}
}
+115
View File
@@ -0,0 +1,115 @@
import type { Locator, Page } from "@playwright/test";
export class VitalsPage {
readonly bodyHeading: Locator;
readonly environmentHeading: Locator;
readonly systems: Locator;
readonly history: Locator;
readonly healthMenuButton: Locator;
readonly healthNavigation: Locator;
readonly bodySystems: Locator;
readonly trendMetrics: Locator;
readonly refreshedStatus: Locator;
readonly targetTemperature: Locator;
readonly coolButton: Locator;
readonly autoButton: Locator;
readonly purifierOnButton: Locator;
readonly purifierOffButton: Locator;
constructor(readonly page: Page) {
this.bodyHeading = page.getByRole("heading", { name: "Body", exact: true });
this.environmentHeading = page.getByRole("heading", { name: "Environment", exact: true });
this.systems = page.getByTestId("vital-system-card");
this.history = page.getByTestId("vital-history");
this.healthMenuButton = page.getByRole("button", { name: "Open Health menu" });
this.healthNavigation = page.getByRole("complementary", { name: "Health navigation" });
this.bodySystems = page.getByRole("group", { name: "Body systems", exact: true });
this.trendMetrics = page.getByRole("group", { name: "Trend metric" });
this.refreshedStatus = page
.getByRole("status")
.filter({ hasText: "Sample readings refreshed" });
this.targetTemperature = page.getByRole("slider", { name: "Target temperature" });
this.coolButton = page.getByRole("button", { name: "Cool", exact: true });
this.autoButton = page.getByRole("button", { name: "Auto", exact: true });
this.purifierOnButton = page.getByRole("button", { name: "Air purifier · On" });
this.purifierOffButton = page.getByRole("button", { name: "Air purifier · Off" });
}
async goTo(search = "") {
await this.page.goto(`/vitals${search}`, { waitUntil: "networkidle" });
}
async navigateTo(section: "Body" | "Environment") {
if (await this.healthMenuButton.isVisible()) await this.healthMenuButton.click();
await this.healthNavigation.getByRole("button", { name: section, exact: true }).click();
}
async selectSystem(name: string) {
await this.bodySystems.getByRole("button", { name, exact: true }).click();
}
async openFirstSystem() {
await this.systems.first().click();
}
systemDialog(name: string) {
return this.page.getByRole("dialog", { name });
}
async closeSystem(name: string) {
await this.systemDialog(name)
.getByRole("button", { name: "Close", exact: true })
.last()
.click();
}
async showLabHistory() {
await this.page.getByRole("button", { name: "Lab history", exact: true }).click();
}
async showSystems() {
await this.page.getByRole("button", { name: "Systems", exact: true }).click();
}
async selectTrendPeriod(period: string) {
await this.page.getByRole("button", { name: period, exact: true }).click();
}
async selectTrendMetric(name: string) {
await this.trendMetrics.getByRole("button", { name, exact: true }).click();
}
trendChart(period: string, metric: string) {
return this.page.getByRole("img", {
name: `${period} ${metric} simulated environmental readings`,
});
}
async openMetric(name: string) {
await this.page.getByTestId("vital-metric").filter({ hasText: name }).click();
}
metricDialog(name: string) {
return this.page.getByRole("dialog", { name });
}
async closeMetric(name: string) {
await this.metricDialog(name).getByRole("button", { name: "Back to overview" }).click();
}
async syncReadings() {
await this.page.getByRole("button", { name: "Sync now", exact: true }).click();
}
async openClimateControl() {
await this.page.getByRole("button", { name: "Climate control", exact: true }).click();
}
async enableCooling() {
await this.coolButton.click();
}
async turnOffPurifier() {
await this.purifierOnButton.click();
}
}
+61
View File
@@ -0,0 +1,61 @@
import { expect, type Locator, type Page } from "@playwright/test";
type Destination = "Home" | "Vitals" | "Messages" | "Journal" | "Reader" | "Research";
export class WorkspacePage {
readonly manageAccounts: Locator;
readonly toggleSidebar: Locator;
readonly mobileSidebar: Locator;
readonly closeSidebar: Locator;
readonly deckManager: Locator;
readonly researchChat: Locator;
readonly researchReady: Locator;
constructor(readonly page: Page) {
this.manageAccounts = page.getByRole("button", { name: "Manage connected accounts" });
this.toggleSidebar = page.getByRole("button", { name: "Toggle sidebar" });
this.mobileSidebar = page.locator('[data-mobile="true"][data-sidebar="sidebar"]');
this.closeSidebar = page.getByRole("button", { name: "Close sidebar", exact: true });
this.deckManager = page.getByRole("button", { name: "Deck manager", exact: true });
this.researchChat = page.getByRole("button", { name: "Research chat", exact: true });
this.researchReady = page.getByRole("button", { name: "Rename", includeHidden: true });
}
navigationLink(destination: Destination) {
return this.page.getByRole("link", { name: destination, exact: true }).first();
}
async navigateTo(destination: Destination) {
const link = this.navigationLink(destination);
if (!(await link.isVisible())) await this.ensureSidebarOpen();
await link.click();
}
async ensureSidebarOpen() {
if (!(await this.manageAccounts.isVisible())) await this.toggleSidebar.click();
}
async showDeckManagement() {
await expect(this.researchReady).toBeEnabled();
await this.ensureSidebarOpen();
await this.deckManager.click();
}
async showResearch() {
await expect(this.researchReady).toBeEnabled();
await this.ensureSidebarOpen();
await this.researchChat.click();
}
async closeMobileSidebar() {
if (await this.mobileSidebar.isVisible()) {
await this.closeSidebar.click();
await expect(this.mobileSidebar).toBeHidden();
}
}
async openConnectedAccounts() {
await this.ensureSidebarOpen();
await this.manageAccounts.click();
}
}