refactor: organize E2E tests with page objects
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user