- Uploads: one at a time per user and ten an hour, each refused only after its body arrived; the history read an entry at a time (32 MiB cap), a Watch later list capped at YouTube's 5000, a user's kept videos at 200k; any unreadable shape is a 422, not a 500. - Quota: Watch later lookups stop at the backfill reserve and at a per-user daily share; what is not asked about waits, known videos still go in, and the shorts job retries the rest. - Staging and settling share one per-user advisory lock (no deadlock); a run settles only the rows it read, so a row staged meanwhile is never called gone. - A failed run's notice names a translated failure, never the upstream's text. - The status says when an import is queued; the page watches it to its end even when left, then reads the feed, its counts and the playlists again. - Settings: one live region, the zone kept (unavailable) while a run goes, focus handed back, a missed drop kept from the browser, an oversize file refused before it is sent. - Scheduler and run lists show Takeout runs by the importing user.
60 lines
3.0 KiB
TypeScript
60 lines
3.0 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
||
import { openApp, gotoPage, seeded } from "./helpers";
|
||
|
||
// Settings › Account › YouTube history: a Google Takeout watch history, uploaded through the real
|
||
// picker, is read, imported in the background, and the section says what it did — a video the
|
||
// catalog knows is marked watched, one it does not know is remembered for later. The seed clears
|
||
// the account's kept Takeout videos each run, but the projects share one account: a later one's
|
||
// import of the same video adds nothing new, so what each adds is counted against what was there
|
||
// before it.
|
||
test("a Takeout watch history uploads, imports, and the section says what it did", async ({
|
||
page,
|
||
}) => {
|
||
await openApp(page);
|
||
await gotoPage(page, "settings");
|
||
await page.getByRole("tab", { name: "Account" }).click();
|
||
await expect(page.getByRole("heading", { name: "YouTube history" })).toBeVisible();
|
||
// The first project's upload is an import already made: its "New import" opens the drop zone.
|
||
const zone = page.getByRole("button", { name: "Drop the files here, or choose them" });
|
||
const again = page.getByRole("button", { name: "New import" });
|
||
await expect(zone.or(again)).toBeVisible();
|
||
if (await again.isVisible()) await again.click();
|
||
await expect(zone).toBeVisible();
|
||
const before = (await (await page.request.get("/api/takeout")).json()) as {
|
||
watched?: Record<string, number>;
|
||
};
|
||
const watchedBefore = before.watched?.watched ?? 0;
|
||
// A video the catalog knows and the Shorts probe has cleared, chosen by the seed (one not probed
|
||
// yet would wait for the probe): the next one no earlier project of this run has imported.
|
||
const known = seeded("takeout_known")[watchedBefore];
|
||
expect(known, "the seed chose too few videos for every project").toBeTruthy();
|
||
|
||
// Watched now: later than any state the seed set, which the import would otherwise leave alone.
|
||
const now = new Date().toISOString();
|
||
const history = [
|
||
{ titleUrl: `https://www.youtube.com/watch?v=${known}`, time: now },
|
||
{ titleUrl: "https://www.youtube.com/watch?v=e2eUnknown0", time: now },
|
||
{
|
||
titleUrl: "https://www.youtube.com/watch?v=e2eAdvert00",
|
||
time: now,
|
||
details: [{ name: "From Google Ads" }],
|
||
},
|
||
];
|
||
const uploaded = page.waitForResponse(
|
||
(r) => r.url().endsWith("/api/takeout") && r.request().method() === "POST",
|
||
);
|
||
await page.locator('input[type="file"][accept=".json,.csv"]').setInputFiles({
|
||
name: "watch-history.json",
|
||
mimeType: "application/json",
|
||
buffer: Buffer.from(JSON.stringify(history)),
|
||
});
|
||
expect((await uploaded).ok()).toBeTruthy();
|
||
|
||
await expect(page.getByText("watch-history.json: 2 videos read")).toBeVisible();
|
||
const line = page.getByTestId("takeout-last");
|
||
await expect(line).toContainText("Last import:");
|
||
await expect(line).toContainText(`${watchedBefore + 1} watched`);
|
||
await expect(line).toContainText("1 remembered");
|
||
await expect(page.getByRole("button", { name: "New import" })).toBeVisible();
|
||
});
|