feat: publish shared task and reply activities through MCP
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
export class ActivitiesPage {
|
||||
constructor(readonly page: Page) {}
|
||||
card(id: string) {
|
||||
return this.page.locator(`[data-activity-id="${id}"]`);
|
||||
}
|
||||
async expand(id: string) {
|
||||
await this.card(id).getByRole("button", { expanded: false }).click();
|
||||
}
|
||||
async openReply(id: string) {
|
||||
await this.expand(id);
|
||||
await this.card(id)
|
||||
.getByRole("link", { name: /^Review reply to / })
|
||||
.click();
|
||||
}
|
||||
complete(title: string) {
|
||||
return this.page.getByRole("button", { name: `Complete ${title}`, exact: true });
|
||||
}
|
||||
restore(title: string) {
|
||||
return this.page.getByRole("button", { name: `Restore ${title}`, exact: true });
|
||||
}
|
||||
get draft() {
|
||||
return this.page.getByRole("textbox", { name: "Reply draft", exact: true });
|
||||
}
|
||||
get saveDraft() {
|
||||
return this.page.getByRole("button", { name: "Save draft", exact: true });
|
||||
}
|
||||
get useProposedDraft() {
|
||||
return this.page.getByRole("button", { name: "Use proposed draft", exact: true });
|
||||
}
|
||||
get later() {
|
||||
return this.page.getByRole("button", { name: "Later", exact: true });
|
||||
}
|
||||
get bringBack() {
|
||||
return this.page.getByRole("button", { name: "Bring back", exact: true });
|
||||
}
|
||||
get notNeeded() {
|
||||
return this.page.getByRole("button", { name: "Not needed", exact: true });
|
||||
}
|
||||
get sources() {
|
||||
return this.page.getByRole("region", { name: "Source excerpts" });
|
||||
}
|
||||
get details() {
|
||||
return this.page.getByRole("region", { name: "Message details" });
|
||||
}
|
||||
action(title: string) {
|
||||
return this.page
|
||||
.getByRole("navigation", { name: "Conversation actions" })
|
||||
.getByRole("button", { name: title, exact: true });
|
||||
}
|
||||
async saveReply(text: string) {
|
||||
await this.draft.fill(text);
|
||||
await this.saveDraft.click();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, type Locator, type Page } from "@playwright/test";
|
||||
|
||||
export class AuthPage {
|
||||
readonly name: Locator;
|
||||
@@ -24,7 +24,8 @@ export class AuthPage {
|
||||
}
|
||||
|
||||
async goTo(mode: "login" | "setup" = "login") {
|
||||
await this.page.goto(`/${mode}`, { waitUntil: "networkidle" });
|
||||
await this.page.goto(`/${mode}`);
|
||||
await expect(this.email).toBeVisible();
|
||||
}
|
||||
|
||||
async signIn(email: string, password: string) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { navigateHydrated } from "./ready";
|
||||
|
||||
export class JournalPage {
|
||||
readonly page: Page;
|
||||
@@ -29,7 +30,7 @@ export class JournalPage {
|
||||
this.summary = this.summaryDialog.getByRole("textbox", { name: "Entry summary" });
|
||||
}
|
||||
async goTo(search = "") {
|
||||
await this.page.goto(`/journal${search}`);
|
||||
await navigateHydrated(this.page, `/journal${search}`);
|
||||
}
|
||||
async correctWeight(weight: string) {
|
||||
await this.weightEntries.getByRole("button", { name: "Correct" }).click();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, type Locator, type Page } from "@playwright/test";
|
||||
|
||||
export class OnboardingPage {
|
||||
readonly name: Locator;
|
||||
@@ -12,11 +12,14 @@ export class OnboardingPage {
|
||||
}
|
||||
|
||||
async goTo() {
|
||||
await this.page.goto("/onboarding", { waitUntil: "networkidle" });
|
||||
await this.page.goto("/onboarding");
|
||||
await expect(
|
||||
this.name.or(this.page.getByRole("textbox", { name: "Home input" })),
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
async complete() {
|
||||
await this.page.waitForLoadState("networkidle");
|
||||
await expect(this.continueButton).toBeEnabled();
|
||||
await this.continueButton.click();
|
||||
await this.openWorkspace.click();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, type Locator, type Page } from "@playwright/test";
|
||||
import { navigateHydrated } from "./ready";
|
||||
|
||||
export class ReaderPage {
|
||||
readonly rows: Locator;
|
||||
@@ -30,7 +31,9 @@ export class ReaderPage {
|
||||
}
|
||||
|
||||
async goTo(search = "") {
|
||||
await this.page.goto(`/inbox${search}`, { waitUntil: "networkidle" });
|
||||
await navigateHydrated(this.page, `/inbox${search}`);
|
||||
if (new URL(this.page.url()).pathname === "/inbox")
|
||||
await expect(this.searchInput).toBeVisible();
|
||||
}
|
||||
|
||||
article(title: string) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Locator, Page } from "@playwright/test";
|
||||
import { expect, type Locator, type Page } from "@playwright/test";
|
||||
import { navigateHydrated, waitForWorkspaceHydration } from "./ready";
|
||||
|
||||
export class VitalsPage {
|
||||
readonly bodyHeading: Locator;
|
||||
@@ -36,7 +37,16 @@ export class VitalsPage {
|
||||
}
|
||||
|
||||
async goTo(search = "") {
|
||||
await this.page.goto(`/vitals${search}`, { waitUntil: "networkidle" });
|
||||
await navigateHydrated(this.page, `/vitals${search}`);
|
||||
if (new URL(this.page.url()).pathname === "/vitals") await this.waitUntilReady();
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await Promise.all([waitForWorkspaceHydration(this.page), this.page.reload()]);
|
||||
}
|
||||
|
||||
async waitUntilReady() {
|
||||
await expect(this.bodyHeading.or(this.environmentHeading)).toBeVisible();
|
||||
}
|
||||
|
||||
async navigateTo(section: "Body" | "Environment") {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Page } from "@playwright/test";
|
||||
|
||||
/** Register before navigation: this stream starts in a client effect after hydration. */
|
||||
export function waitForWorkspaceHydration(page: Page) {
|
||||
return page.waitForResponse((response) => {
|
||||
if (response.status() !== 200) return false;
|
||||
const path = new URL(response.url()).pathname;
|
||||
return (
|
||||
path === "/api/activities/events" ||
|
||||
(response.request().isNavigationRequest() &&
|
||||
["/login", "/setup", "/onboarding"].includes(path))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function navigateHydrated(page: Page, url: string) {
|
||||
await Promise.all([waitForWorkspaceHydration(page), page.goto(url)]);
|
||||
}
|
||||
Reference in New Issue
Block a user