feat: publish shared task and reply activities through MCP
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { activityProposals, expect, test } from "../activity-fixtures";
|
||||
import { ActivitiesPage } from "../models/ActivitiesPage";
|
||||
|
||||
test("shares published chores and reply activities with Home, Messages and MCP without losing user changes", async ({
|
||||
page,
|
||||
context,
|
||||
homePage,
|
||||
workspacePage,
|
||||
messagesPage,
|
||||
activitiesPage,
|
||||
activityMcp,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await expect(activitiesPage.card("buy-detergent")).toHaveCount(0);
|
||||
expect((await activityMcp.context()).activities).toEqual([]);
|
||||
await activityMcp.publish();
|
||||
await expect(activitiesPage.card("buy-detergent")).toBeVisible();
|
||||
await expect(activitiesPage.card("review-times")).toBeVisible();
|
||||
await expect(activitiesPage.card("send-documents")).toBeVisible();
|
||||
await activitiesPage.complete("Buy detergent").click();
|
||||
await expect(activitiesPage.card("buy-detergent")).toHaveAttribute("data-status", "done");
|
||||
await activitiesPage.openReply("review-times");
|
||||
await expect(page).toHaveURL(/\/support\?.*activity=review-times/);
|
||||
await expect(activitiesPage.details).toContainText(
|
||||
"Read the review materials and choose two times",
|
||||
);
|
||||
await expect(activitiesPage.sources).toContainText(
|
||||
"Please review the materials and suggest two times.",
|
||||
);
|
||||
await expect(activitiesPage.sources).toContainText("not the full conversation history");
|
||||
await expect(activitiesPage.action("Send the completed documents")).toBeVisible();
|
||||
const edited = "I checked the materials. Thursday at 14:00 works for me.";
|
||||
await activitiesPage.saveReply(edited);
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await activityMcp.context()).activities.find((item) => item.id === "review-times")
|
||||
?.userOverrides.draft,
|
||||
)
|
||||
.toBe(edited);
|
||||
await activitiesPage.later.click();
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await activityMcp.context()).activities.find((item) => item.id === "review-times")
|
||||
?.userState,
|
||||
)
|
||||
.toBe("deferred");
|
||||
const before = await activityMcp.context();
|
||||
expect(before.activities.find((item) => item.id === "send-documents")?.userState).toBe(
|
||||
"available",
|
||||
);
|
||||
const refreshed = activityProposals.map((proposal) =>
|
||||
proposal.kind === "reply"
|
||||
? { ...proposal, reply: { ...proposal.reply, draft: "A newer agent suggestion" } }
|
||||
: proposal,
|
||||
);
|
||||
await activityMcp.publish(refreshed);
|
||||
const after = await activityMcp.context();
|
||||
expect(after.activities).toHaveLength(3);
|
||||
expect(after.activities.find((item) => item.id === "review-times")).toMatchObject({
|
||||
userState: "deferred",
|
||||
userOverrides: { draft: edited },
|
||||
effective: { reply: { draft: edited } },
|
||||
proposal: { reply: { draft: "A newer agent suggestion" } },
|
||||
});
|
||||
expect(after.activities.find((item) => item.id === "buy-detergent")?.userState).toBe("completed");
|
||||
await page.reload();
|
||||
await expect(activitiesPage.draft).toHaveValue(edited);
|
||||
await expect(
|
||||
activitiesPage.details.getByRole("button", {
|
||||
name: /^(Send|Mark complete|Try sending)$/,
|
||||
exact: true,
|
||||
}),
|
||||
).toHaveCount(0);
|
||||
await activitiesPage.action("Send the completed documents").click();
|
||||
await expect(activitiesPage.draft).toHaveValue("A newer agent suggestion");
|
||||
await messagesPage.showConversationList();
|
||||
await workspacePage.navigateTo("Home");
|
||||
await expect(activitiesPage.card("review-times")).toContainText("Set aside");
|
||||
const device = await context.newPage();
|
||||
try {
|
||||
const other = new ActivitiesPage(device);
|
||||
await device.goto("/");
|
||||
await expect(other.card("buy-detergent")).toHaveAttribute("data-status", "done");
|
||||
await activityMcp.publish([
|
||||
{
|
||||
id: "later-chore",
|
||||
kind: "task",
|
||||
title: "Take the recycling out",
|
||||
nextAction: "Bring the paper bag downstairs",
|
||||
sources: [],
|
||||
},
|
||||
]);
|
||||
await expect(activitiesPage.card("later-chore")).toBeVisible();
|
||||
await expect(other.card("later-chore")).toBeVisible();
|
||||
await device.reload();
|
||||
await expect(other.card("later-chore")).toBeVisible();
|
||||
} finally {
|
||||
await device.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("retries publication without duplicates and rejects invalid batches without partial writes", async ({
|
||||
activityMcp,
|
||||
}) => {
|
||||
const input = {
|
||||
requestId: "stable-publication",
|
||||
expectedRevision: 0,
|
||||
activities: activityProposals,
|
||||
};
|
||||
const receipt = await activityMcp.tool("publish_activities", input);
|
||||
expect(receipt.ok).toBe(true);
|
||||
expect(await activityMcp.tool("publish_activities", input)).toEqual(receipt);
|
||||
expect(
|
||||
await activityMcp.tool("publish_activities", { ...input, requestId: "stale" }),
|
||||
).toMatchObject({ ok: false, error: { code: "revision-conflict" } });
|
||||
const snapshot = await activityMcp.context();
|
||||
expect(
|
||||
await activityMcp.tool("publish_activities", {
|
||||
requestId: "invalid",
|
||||
expectedRevision: snapshot.revision,
|
||||
activities: [
|
||||
{
|
||||
id: "valid-sibling",
|
||||
kind: "task",
|
||||
title: "Valid sibling",
|
||||
nextAction: "Do the chore",
|
||||
sources: [],
|
||||
},
|
||||
{
|
||||
id: "invalid-sibling",
|
||||
kind: "reply",
|
||||
title: "Missing reply target",
|
||||
nextAction: "Check",
|
||||
sources: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toMatchObject({ ok: false, error: { code: "invalid-input" } });
|
||||
expect(await activityMcp.context()).toEqual(snapshot);
|
||||
});
|
||||
@@ -14,7 +14,7 @@ test("creates the owner once and enters onboarding with a real session", async (
|
||||
db.$client.close();
|
||||
await context.clearCookies();
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(authPage.createAccountButton).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/setup$/);
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await authPage.createAccount({
|
||||
@@ -24,7 +24,7 @@ test("creates the owner once and enters onboarding with a real session", async (
|
||||
setupCode: "isolated-e2e-setup-token-not-for-production",
|
||||
});
|
||||
await expect(page).toHaveURL(/\/onboarding$/);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(onboardingPage.continueButton).toBeEnabled();
|
||||
await onboardingPage.complete();
|
||||
await expect(homePage.greeting("Yuta")).toBeVisible();
|
||||
});
|
||||
@@ -70,7 +70,7 @@ test("signs in and revokes the session on sign out", async ({
|
||||
await authPage.password.fill("E2E-only-passphrase-2026");
|
||||
await authPage.signInButton.click();
|
||||
await expect(page).toHaveURL("/");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(workspacePage.manageAccounts).toBeVisible();
|
||||
const token = (await context.cookies()).find((cookie) => cookie.name === "workspace_session");
|
||||
expect(token?.httpOnly).toBe(true);
|
||||
expect(token?.sameSite).toBe("Lax");
|
||||
@@ -99,7 +99,7 @@ test("requires onboarding and remembers its completion and name", async ({
|
||||
try {
|
||||
expect((await request.get("/api/research/events")).status()).toBe(403);
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(onboardingPage.name).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/onboarding$/);
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await onboardingPage.name.fill("Yuta Test");
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { expect, test } from "../fixtures";
|
||||
import { expect, test } from "../activity-fixtures";
|
||||
|
||||
test.beforeEach(async ({ activityMcp }) => {
|
||||
await activityMcp.publish();
|
||||
});
|
||||
|
||||
test("executes routine steps and records a measurement in the shared journal", async ({
|
||||
page,
|
||||
@@ -8,7 +12,7 @@ test("executes routine steps and records a measurement in the shared journal", a
|
||||
workspacePage,
|
||||
}, testInfo) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await homePage.morningRoutine.click();
|
||||
await expect(page).toHaveURL(/activity=task/);
|
||||
await homePage.routineStep(/Drink water/).check();
|
||||
@@ -30,15 +34,16 @@ test("shows the day flow and conversation context at desktop width", async ({
|
||||
page,
|
||||
homePage,
|
||||
messagesPage,
|
||||
activitiesPage,
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1440, height: 1080 });
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await expect(homePage.todayPlan).toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath("home-desktop.png") });
|
||||
await messagesPage.goTo("?conversation=design-review");
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(messagesPage.reply).toBeVisible();
|
||||
await messagesPage.goTo("?activity=review-times&view=all");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await expect(activitiesPage.draft).toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath("messages-desktop.png") });
|
||||
});
|
||||
|
||||
@@ -49,7 +54,7 @@ test("previews a natural-language memo and shares the executed record with journ
|
||||
workspacePage,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
const memo = "明日のレビューは導線から確認する";
|
||||
await homePage.previewCommand(`Note: ${memo}`);
|
||||
const execute = homePage.applyAction;
|
||||
@@ -63,7 +68,7 @@ test("previews a natural-language memo and shares the executed record with journ
|
||||
|
||||
test("keeps the home usable on narrow screens", async ({ page, a11y, homePage }, testInfo) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
for (const width of [320, 375, 414, 768]) {
|
||||
await page.setViewportSize({ width, height: 850 });
|
||||
await expect(homePage.input).toBeVisible();
|
||||
@@ -73,80 +78,77 @@ test("keeps the home usable on narrow screens", async ({ page, a11y, homePage },
|
||||
}
|
||||
});
|
||||
|
||||
test("adds tasks only after confirmation and can defer and restore them", async ({
|
||||
test("adds tasks only after confirmation and persists their deferred and completed state", async ({
|
||||
page,
|
||||
homePage,
|
||||
activitiesPage,
|
||||
activityMcp,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const complete = homePage.completeTask("grocery shopping");
|
||||
const complete = activitiesPage.complete("grocery shopping");
|
||||
await homePage.previewCommand("Add grocery shopping");
|
||||
await expect(complete).toHaveCount(0);
|
||||
await homePage.applyAction.click();
|
||||
await expect(complete).toBeVisible();
|
||||
await homePage.previewCommand("Defer grocery shopping");
|
||||
await expect(complete).toBeVisible();
|
||||
await homePage.applyAction.click();
|
||||
await expect(complete).toHaveCount(0);
|
||||
await homePage.restoreTask("grocery shopping").click();
|
||||
await expect
|
||||
.poll(
|
||||
async () =>
|
||||
(await activityMcp.context()).activities.find(
|
||||
(item) => item.effective.title === "grocery shopping",
|
||||
)?.userState,
|
||||
)
|
||||
.toBe("deferred");
|
||||
await page.reload();
|
||||
await complete.click();
|
||||
await expect(complete).toHaveCount(0);
|
||||
await expect(homePage.restoreTask("grocery shopping")).toBeVisible();
|
||||
await expect(activitiesPage.restore("grocery shopping")).toBeVisible();
|
||||
});
|
||||
|
||||
test("opens a conversation page from home and retains outcomes on return", async ({
|
||||
test("opens a reply activity from home and retains its state through history and reload", async ({
|
||||
page,
|
||||
a11y,
|
||||
homePage,
|
||||
messagesPage,
|
||||
workspacePage,
|
||||
activitiesPage,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const conversationLink = homePage.conversation("Haruka Aoki");
|
||||
await expect(conversationLink).toHaveAttribute("href", /conversation=design-review/);
|
||||
await conversationLink.click();
|
||||
await expect(page).toHaveURL(/\/support\?.*conversation=design-review/);
|
||||
await expect(messagesPage.dialogs).toHaveCount(0);
|
||||
await expect(messagesPage.reply).toBeVisible();
|
||||
await activitiesPage.openReply("review-times");
|
||||
await expect(page).toHaveURL(/activity=review-times/);
|
||||
await expect(activitiesPage.draft).toBeVisible();
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await page.goBack();
|
||||
await expect(conversationLink).toBeVisible();
|
||||
await expect(activitiesPage.card("review-times")).toBeVisible();
|
||||
await page.goForward();
|
||||
await expect(messagesPage.reply).toBeVisible();
|
||||
await expect(activitiesPage.draft).toBeVisible();
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(messagesPage.reply).toBeVisible();
|
||||
await messagesPage.leaveForLater.click();
|
||||
await workspacePage.navigateTo("Home");
|
||||
await expect(conversationLink).toContainText("Set aside");
|
||||
await conversationLink.click();
|
||||
await expect(page).toHaveURL(/conversation=design-review/);
|
||||
await expect(messagesPage.moveToInbox).toBeVisible();
|
||||
await messagesPage.moveToInbox.click();
|
||||
await expect(activitiesPage.draft).toBeVisible();
|
||||
await activitiesPage.later.click();
|
||||
await messagesPage.showConversationList();
|
||||
await workspacePage.navigateTo("Home");
|
||||
await expect(conversationLink).toBeVisible();
|
||||
await expect(activitiesPage.card("review-times")).toContainText("Set aside");
|
||||
await activitiesPage.openReply("review-times");
|
||||
await activitiesPage.bringBack.click();
|
||||
await expect(activitiesPage.later).toBeVisible();
|
||||
});
|
||||
|
||||
test("updates the prose brief as priorities change and opens the referenced conversation", async ({
|
||||
page,
|
||||
test("updates the prose brief as activities change and opens the referenced reply", async ({
|
||||
homePage,
|
||||
messagesPage,
|
||||
workspacePage,
|
||||
activitiesPage,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const brief = homePage.dailyBrief;
|
||||
await expect(brief).toContainText("Review the documents");
|
||||
await expect(brief).toContainText("3 conversations");
|
||||
await homePage.completeTask("Review the documents").click();
|
||||
await expect(brief).not.toContainText("Review the documents");
|
||||
await homePage.openBriefConversation("Haruka Aoki");
|
||||
await messagesPage.leaveForLater.click();
|
||||
await expect(brief).toContainText("Buy detergent");
|
||||
await activitiesPage.complete("Buy detergent").click();
|
||||
await expect(brief).not.toContainText("Buy detergent");
|
||||
await brief.getByRole("button", { name: "Propose review times", exact: true }).click();
|
||||
await activitiesPage.later.click();
|
||||
await messagesPage.showConversationList();
|
||||
await workspacePage.navigateTo("Home");
|
||||
await expect(brief).toContainText("2 conversations");
|
||||
await expect(brief).not.toContainText("Haruka Aoki");
|
||||
await expect(brief).not.toContainText("Propose review times");
|
||||
await homePage.previewCommand("再開地点:資料の2ページ目から");
|
||||
await homePage.applyAction.click();
|
||||
await expect(brief).toContainText("資料の2ページ目から");
|
||||
@@ -156,32 +158,19 @@ test("keeps completed actions in their original positions with visible outcomes"
|
||||
page,
|
||||
a11y,
|
||||
homePage,
|
||||
messagesPage,
|
||||
workspacePage,
|
||||
activitiesPage,
|
||||
}, testInfo) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const titles = homePage.flowTitles;
|
||||
const before = await titles.allTextContents();
|
||||
await homePage.completeTask("Review the documents").click();
|
||||
await expect(homePage.restoreTask("Review the documents")).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"true",
|
||||
);
|
||||
await expect(homePage.completedTask("Review the documents")).toBeVisible();
|
||||
await homePage.conversation("Haruka Aoki").click();
|
||||
await messagesPage.reviewReply.click();
|
||||
await messagesPage.trySending.click();
|
||||
await workspacePage.navigateTo("Home");
|
||||
await expect(homePage.conversation("Haruka Aoki")).toContainText("Reply recorded");
|
||||
await expect(activitiesPage.card("send-documents")).toBeVisible();
|
||||
const before = await homePage.flowTitles.allTextContents();
|
||||
await activitiesPage.complete("Buy detergent").click();
|
||||
await expect(activitiesPage.restore("Buy detergent")).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(activitiesPage.card("buy-detergent")).toContainText("Completed");
|
||||
await homePage.recordMeasurement("65.2");
|
||||
await expect(homePage.recordedWeight("65.2")).toBeVisible();
|
||||
expect(await titles.allTextContents()).toEqual(before);
|
||||
expect(await homePage.flowTitles.allTextContents()).toEqual(before);
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await page.screenshot({ path: testInfo.outputPath("completion-in-place.png") });
|
||||
await homePage.restoreTask("Review the documents").click();
|
||||
await expect(homePage.completeTask("Review the documents")).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"false",
|
||||
);
|
||||
await activitiesPage.restore("Buy detergent").click();
|
||||
await expect(activitiesPage.complete("Buy detergent")).toHaveAttribute("aria-pressed", "false");
|
||||
});
|
||||
|
||||
@@ -1,71 +1,68 @@
|
||||
import { expect, test } from "../fixtures";
|
||||
import { expect, test } from "../activity-fixtures";
|
||||
|
||||
const reviewSubject = "Schedule next week’s design review";
|
||||
const coffeeSubject = "Catch up over coffee";
|
||||
test.beforeEach(async ({ activityMcp }) => {
|
||||
await activityMcp.publish();
|
||||
});
|
||||
|
||||
test("reviews an edited reply and keeps the simulated result in history", async ({
|
||||
test("saves an edited reply without recording a simulated send", async ({
|
||||
page,
|
||||
a11y,
|
||||
messagesPage,
|
||||
activitiesPage,
|
||||
activityMcp,
|
||||
}) => {
|
||||
await messagesPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
const rows = messagesPage.rows;
|
||||
await expect(rows).toHaveCount(3);
|
||||
await messagesPage.conversation(reviewSubject).click();
|
||||
await messagesPage.chooseReplySuggestion(/^Polite/);
|
||||
const reply = messagesPage.reply;
|
||||
await expect(reply).toHaveValue(/Thank you for sharing/);
|
||||
await messagesPage.goTo("?activity=review-times&view=all");
|
||||
const edited = "I reviewed the materials. Let’s discuss the first-time experience.";
|
||||
await reply.fill(edited);
|
||||
const slot = "Mon, Oct 5, 14:00–14:30";
|
||||
await messagesPage.addSuggestedTime(slot);
|
||||
await expect(reply).toHaveValue(`${edited}\n\nWould ${slot} work for you?`);
|
||||
await messagesPage.reviewReply.click();
|
||||
const dialog = messagesPage.reviewDialog;
|
||||
await expect(dialog).toContainText(edited);
|
||||
await expect(dialog).toContainText(slot);
|
||||
await expect(dialog).toContainText("No message will be sent");
|
||||
await activitiesPage.saveReply(edited);
|
||||
await expect(page.getByRole("status").filter({ hasText: "Draft saved" })).toBeVisible();
|
||||
await expect(activitiesPage.details).toContainText("Sending messages is not connected yet.");
|
||||
await expect(
|
||||
activitiesPage.details.getByRole("button", { name: "Mark complete", exact: true }),
|
||||
).toHaveCount(0);
|
||||
await page.reload();
|
||||
await expect(activitiesPage.draft).toHaveValue(edited);
|
||||
const activity = (await activityMcp.context()).activities.find(
|
||||
(item) => item.id === "review-times",
|
||||
);
|
||||
expect(activity).toMatchObject({ userState: "available", userOverrides: { draft: edited } });
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await messagesPage.editReply.click();
|
||||
await expect(reply).toHaveValue(new RegExp(edited));
|
||||
await messagesPage.reviewReply.click();
|
||||
await messagesPage.trySending.click();
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(messagesPage.conversation(reviewSubject)).toHaveCount(0);
|
||||
await messagesPage.selectView("Archive");
|
||||
await messagesPage.conversation(reviewSubject).click();
|
||||
await expect(messagesPage.outcome).toContainText(edited);
|
||||
await expect(messagesPage.outcome).toContainText(slot);
|
||||
});
|
||||
|
||||
test("defers a conversation and restores it to the current queue", async ({
|
||||
page,
|
||||
test("defers one reply while keeping another action in the same conversation available", async ({
|
||||
messagesPage,
|
||||
activitiesPage,
|
||||
activityMcp,
|
||||
}) => {
|
||||
await messagesPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await messagesPage.conversation(coffeeSubject).click();
|
||||
await expect(messagesPage.conversation(coffeeSubject)).toHaveAttribute("aria-current", "true");
|
||||
await messagesPage.reply.fill("土曜の予定を確認してから返事するね。");
|
||||
await messagesPage.leaveForLater.click();
|
||||
await expect(messagesPage.conversation(coffeeSubject)).toHaveCount(0);
|
||||
await messagesPage.selectView("Set aside");
|
||||
await messagesPage.conversation(coffeeSubject).click();
|
||||
await expect(messagesPage.reply).toHaveValue("土曜の予定を確認してから返事するね。");
|
||||
await messagesPage.moveToInbox.click();
|
||||
await messagesPage.showConversationList();
|
||||
await expect(messagesPage.conversation(coffeeSubject)).toBeVisible();
|
||||
await expect(messagesPage.inbox).toHaveAttribute("aria-pressed", "true");
|
||||
await messagesPage.goTo("?activity=review-times&view=all");
|
||||
await activitiesPage.saveReply("土曜の予定を確認してから返事するね。");
|
||||
await expect(activitiesPage.useProposedDraft).toBeVisible();
|
||||
await activitiesPage.later.click();
|
||||
await expect(activitiesPage.bringBack).toBeVisible();
|
||||
await activitiesPage.action("Send the completed documents").click();
|
||||
await expect(activitiesPage.details).toContainText("Ready when you are");
|
||||
const snapshot = await activityMcp.context();
|
||||
expect(snapshot.activities.find((item) => item.id === "review-times")?.userState).toBe(
|
||||
"deferred",
|
||||
);
|
||||
expect(snapshot.activities.find((item) => item.id === "send-documents")?.userState).toBe(
|
||||
"available",
|
||||
);
|
||||
await activitiesPage.action("Propose review times").click();
|
||||
await expect(activitiesPage.draft).toHaveValue("土曜の予定を確認してから返事するね。");
|
||||
await activitiesPage.bringBack.click();
|
||||
await expect(activitiesPage.later).toBeVisible();
|
||||
});
|
||||
|
||||
test("keeps corrections as new records and summarizes only selected current facts", async ({
|
||||
page,
|
||||
a11y,
|
||||
journalPage,
|
||||
homePage,
|
||||
workspacePage,
|
||||
}) => {
|
||||
await journalPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await homePage.goTo();
|
||||
await workspacePage.navigateTo("Journal");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await expect(journalPage.saveEntry).toBeDisabled();
|
||||
await journalPage.entryText.fill("次はレビュー資料の導線を確認する");
|
||||
await journalPage.saveEntry.click();
|
||||
@@ -97,16 +94,17 @@ test("fits communication and journal interactions in narrow screens", async ({
|
||||
page,
|
||||
a11y,
|
||||
messagesPage,
|
||||
activitiesPage,
|
||||
journalPage,
|
||||
workspacePage,
|
||||
}, testInfo) => {
|
||||
for (const width of [320, 375, 414, 768]) {
|
||||
await page.setViewportSize({ width, height: 850 });
|
||||
await messagesPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width);
|
||||
await messagesPage.rows.first().click();
|
||||
await expect(messagesPage.reply).toBeVisible();
|
||||
await expect(activitiesPage.draft).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBe(width);
|
||||
if (width === 320) expect((await a11y().analyze()).violations).toEqual([]);
|
||||
await page.screenshot({ path: testInfo.outputPath(`messages-${width}.png`) });
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { expect, test } from "../fixtures";
|
||||
import type { Page } from "@playwright/test";
|
||||
import { expect, test } from "../activity-fixtures";
|
||||
import { waitForWorkspaceHydration } from "../models/ready";
|
||||
|
||||
async function reloadHydrated(page: Page) {
|
||||
await Promise.all([waitForWorkspaceHydration(page), page.reload()]);
|
||||
}
|
||||
|
||||
test.beforeEach(async ({ activityMcp }) => {
|
||||
await activityMcp.publish();
|
||||
});
|
||||
|
||||
test("restores article selection with browser history and reload", async ({ page, readerPage }) => {
|
||||
await readerPage.goTo();
|
||||
@@ -8,7 +18,7 @@ test("restores article selection with browser history and reload", async ({ page
|
||||
await expect(readerPage.articleTitle).toHaveText(heading);
|
||||
const selectedUrl = page.url();
|
||||
expect(new URL(selectedUrl).searchParams.get("article")).toBeTruthy();
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
await reloadHydrated(page);
|
||||
await expect(readerPage.articleTitle).toHaveText(heading ?? "");
|
||||
await page.goBack();
|
||||
await expect(readerPage.workspace).toHaveAttribute("data-reading", "false");
|
||||
@@ -24,14 +34,14 @@ test("restores search and replaces typing history", async ({
|
||||
workspacePage,
|
||||
}) => {
|
||||
await homePage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await workspacePage.navigateTo("Reader");
|
||||
const search = readerPage.searchInput;
|
||||
await search.pressSequentially("browser");
|
||||
await expect(search).toHaveValue("browser");
|
||||
await expect.poll(() => new URL(page.url()).searchParams.get("q")).toBe("browser");
|
||||
const count = await readerPage.rows.count();
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
await reloadHydrated(page);
|
||||
await expect(search).toHaveValue("browser");
|
||||
await expect(readerPage.rows).toHaveCount(count);
|
||||
await page.goBack();
|
||||
@@ -46,7 +56,7 @@ test("restores account dialog without losing underlying article", async ({
|
||||
await readerPage.goTo();
|
||||
await connectedAccounts.open();
|
||||
await expect.poll(() => new URL(page.url()).searchParams.get("accounts")).toBe("true");
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
await reloadHydrated(page);
|
||||
await expect(connectedAccounts.dialog).toBeVisible();
|
||||
await page.goBack();
|
||||
await expect(connectedAccounts.dialog).toHaveCount(0);
|
||||
@@ -56,16 +66,16 @@ test("restores account dialog without losing underlying article", async ({
|
||||
|
||||
test("restores a message view and conversation from its URL", async ({ page, messagesPage }) => {
|
||||
await messagesPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await messagesPage.selectView("All");
|
||||
const row = messagesPage.rows.nth(1);
|
||||
const heading = await messagesPage.rowName(1).innerText();
|
||||
const row = messagesPage.rows.first();
|
||||
const heading = await messagesPage.rowName(0).innerText();
|
||||
await row.click();
|
||||
await expect(messagesPage.toolbarName).toHaveText(heading);
|
||||
const url = page.url();
|
||||
expect(new URL(url).searchParams.get("view")).toBe("all");
|
||||
expect(new URL(url).searchParams.get("conversation")).toBeTruthy();
|
||||
await page.reload({ waitUntil: "networkidle" });
|
||||
expect(new URL(url).searchParams.get("activity")).toBeTruthy();
|
||||
await reloadHydrated(page);
|
||||
await expect(messagesPage.toolbarName).toHaveText(heading ?? "");
|
||||
await expect(messagesPage.workspace).toHaveAttribute("data-reading", "true");
|
||||
});
|
||||
@@ -84,7 +94,7 @@ test("debounces search and cancels pending input on history navigation", async (
|
||||
messagesPage,
|
||||
}) => {
|
||||
await messagesPage.goTo();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
await messagesPage.selectView("All");
|
||||
await expect(page).toHaveURL(/view=all/);
|
||||
await page.clock.install();
|
||||
|
||||
@@ -7,8 +7,7 @@ test("filters body systems and opens lab history", async ({ page, vitalsPage, a1
|
||||
await vitalsPage.selectSystem("Cardiovascular");
|
||||
await expect(vitalsPage.systems).toHaveCount(1);
|
||||
await expect(page).toHaveURL(/vitalTab=Cardiovascular/);
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await vitalsPage.reload();
|
||||
await expect(vitalsPage.systems).toHaveCount(1);
|
||||
await vitalsPage.openFirstSystem();
|
||||
const dialog = vitalsPage.systemDialog("Cardiovascular");
|
||||
@@ -21,8 +20,7 @@ test("filters body systems and opens lab history", async ({ page, vitalsPage, a1
|
||||
await vitalsPage.showLabHistory();
|
||||
await expect(vitalsPage.history).toContainText("Jul 28");
|
||||
await expect(vitalsPage.history).toContainText("93");
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await vitalsPage.reload();
|
||||
await expect(vitalsPage.history).toContainText("Jul 28");
|
||||
await vitalsPage.showSystems();
|
||||
await vitalsPage.selectSystem("Overview");
|
||||
@@ -30,25 +28,19 @@ test("filters body systems and opens lab history", async ({ page, vitalsPage, a1
|
||||
expect((await a11y().analyze()).violations).toEqual([]);
|
||||
});
|
||||
|
||||
test("explores environment charts and simulated climate controls", async ({
|
||||
page,
|
||||
vitalsPage,
|
||||
a11y,
|
||||
}) => {
|
||||
test("explores environment charts and simulated climate controls", async ({ vitalsPage, a11y }) => {
|
||||
await vitalsPage.goTo();
|
||||
await vitalsPage.navigateTo("Environment");
|
||||
await expect(vitalsPage.environmentHeading).toBeVisible();
|
||||
await vitalsPage.selectTrendPeriod("7D");
|
||||
await vitalsPage.selectTrendMetric("CO₂");
|
||||
await expect(vitalsPage.trendChart("7D", "CO₂")).toBeVisible();
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await vitalsPage.reload();
|
||||
await expect(vitalsPage.trendChart("7D", "CO₂")).toBeVisible();
|
||||
await vitalsPage.openMetric("Room temperature");
|
||||
const dialog = vitalsPage.metricDialog("Room temperature");
|
||||
await expect(dialog).toContainText("64.0");
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await vitalsPage.reload();
|
||||
await expect(dialog).toContainText("64.0");
|
||||
await vitalsPage.closeMetric("Room temperature");
|
||||
await vitalsPage.syncReadings();
|
||||
@@ -63,8 +55,7 @@ test("explores environment charts and simulated climate controls", async ({
|
||||
await expect(temperature).toHaveValue("69");
|
||||
await vitalsPage.turnOffPurifier();
|
||||
await expect(vitalsPage.purifierOffButton).toHaveAttribute("aria-pressed", "false");
|
||||
await page.reload();
|
||||
await page.waitForLoadState("networkidle");
|
||||
await vitalsPage.reload();
|
||||
await expect(temperature).toHaveValue("68");
|
||||
await expect(vitalsPage.autoButton).toHaveAttribute("aria-pressed", "true");
|
||||
await expect(vitalsPage.purifierOnButton).toHaveAttribute("aria-pressed", "true");
|
||||
|
||||
Reference in New Issue
Block a user