Build coffee history and experiment workflows with Ask Bean Bro
Test and deploy / test-and-deploy (push) Successful in 1m21s
Test and deploy / test-and-deploy (push) Successful in 1m21s
This commit is contained in:
1 parent
1fd53091ac
commit
8c21750356
28 files changed
+3848
-384
No files matched your search
+459
-42
@@ -7,9 +7,9 @@ import { blankPlan } from "../shared/fields.js";
|
||||
import { renderReportHtml } from "../shared/report.js";
|
||||
|
||||
const browserOptions = { skip: process.env.RUN_BROWSER_TESTS !== "1" };
|
||||
async function session(t) {
|
||||
async function session(t, appOptions = {}) {
|
||||
const { chromium } = await import("playwright");
|
||||
const { app, db } = await setup();
|
||||
const { app, db } = await setup({}, appOptions);
|
||||
const server = app.listen(0, "127.0.0.1");
|
||||
await once(server, "listening");
|
||||
t.after(() => new Promise((r) => server.close(r)));
|
||||
@@ -104,6 +104,7 @@ test(
|
||||
2,
|
||||
"in-app print must not orphan the footer",
|
||||
);
|
||||
const standalone = await page.context().browser().newPage();
|
||||
for (const [label, content, options] of [
|
||||
["downloaded HTML", html, { preferCSSPageSize: true }],
|
||||
[
|
||||
@@ -146,7 +147,7 @@ test(
|
||||
);
|
||||
await page.goto(base + "/app?plan=" + a.id);
|
||||
await page.locator('#plan-form input[name="0.4"]').fill("520");
|
||||
await page.getByRole("link", { name: "▤ Your plans", exact: true }).click();
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page.locator("#plans-body .plan-title-link").first().waitFor();
|
||||
await page.locator('#plan-filters input[name="q"]').fill("Espresso");
|
||||
@@ -163,7 +164,7 @@ test(
|
||||
await page.locator("#header-coffee-name").textContent(),
|
||||
"Espresso plan",
|
||||
);
|
||||
await page.getByRole("link", { name: "▤ Your plans", exact: true }).click();
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page.getByRole("link", { name: "Fruit plan", exact: true }).click();
|
||||
await page.waitForURL("**/app?plan=" + a.id);
|
||||
@@ -258,45 +259,461 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test('browser: Your Plans recovers a deleted remote draft without losing edits', browserOptions, async t => {
|
||||
const {page,base,create,db,errors}=await session(t);
|
||||
const saved=await create(warningPlan(),'Recovered fruit');
|
||||
await page.goto(base+'/app?plan='+saved.id);
|
||||
await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await db.query('DELETE FROM roast_plans WHERE id=$1',[saved.id]);
|
||||
await page.locator('#plan-form input[name="0.4"]').fill('530');
|
||||
await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();
|
||||
await page.waitForURL('**/plans*');
|
||||
await page.getByRole('link',{name:'Recovered fruit',exact:true}).waitFor();
|
||||
const rows=(await db.query('SELECT id,plan FROM roast_plans')).rows;
|
||||
assert.equal(rows.length,1);assert.notEqual(rows[0].id,saved.id);assert.equal(rows[0].plan.fields['0.4'],'530');assert.deepEqual(errors,[]);
|
||||
});
|
||||
test(
|
||||
"browser: Your Plans recovers a deleted remote draft without losing edits",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t);
|
||||
const saved = await create(warningPlan(), "Recovered fruit");
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await db.query("DELETE FROM roast_plans WHERE id=$1", [saved.id]);
|
||||
await page.locator('#plan-form input[name="0.4"]').fill("530");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page
|
||||
.getByRole("link", { name: "Recovered fruit", exact: true })
|
||||
.waitFor();
|
||||
const rows = (await db.query("SELECT id,plan FROM roast_plans")).rows;
|
||||
assert.equal(rows.length, 1);
|
||||
assert.notEqual(rows[0].id, saved.id);
|
||||
assert.equal(rows[0].plan.fields["0.4"], "530");
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
|
||||
test('browser: Your Plans saves to server when browser draft storage is full', browserOptions, async t => {
|
||||
const {page,base,create,db,errors}=await session(t);const saved=await create(warningPlan());
|
||||
await page.goto(base+'/app?plan='+saved.id);await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await page.evaluate(()=>{const original=Storage.prototype.setItem;Storage.prototype.setItem=function(key,value){if(key.startsWith('roastPlannerPlan.v2:'))throw new DOMException('Storage full','QuotaExceededError');return original.call(this,key,value);};});
|
||||
await page.locator('#plan-form input[name="0.4"]').fill('540');await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');
|
||||
assert.equal((await db.query('SELECT plan FROM roast_plans WHERE id=$1',[saved.id])).rows[0].plan.fields['0.4'],'540');assert.deepEqual(errors,[]);
|
||||
});
|
||||
test(
|
||||
"browser: Your Plans saves to server when browser draft storage is full",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t);
|
||||
const saved = await create(warningPlan());
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await page.evaluate(() => {
|
||||
const original = Storage.prototype.setItem;
|
||||
Storage.prototype.setItem = function (key, value) {
|
||||
if (key.startsWith("roastPlannerPlan.v2:"))
|
||||
throw new DOMException("Storage full", "QuotaExceededError");
|
||||
return original.call(this, key, value);
|
||||
};
|
||||
});
|
||||
await page.locator('#plan-form input[name="0.4"]').fill("540");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
assert.equal(
|
||||
(await db.query("SELECT plan FROM roast_plans WHERE id=$1", [saved.id]))
|
||||
.rows[0].plan.fields["0.4"],
|
||||
"540",
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
|
||||
test('browser: rejected sync shows the cause and keeps the edited plan open', browserOptions, async t => {
|
||||
const {page,base,create,db}=await session(t);const saved=await create(warningPlan());await page.goto(base+'/app?plan='+saved.id);await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await page.route('**/api/plans/'+saved.id,async route=>route.request().method()==='PUT'?route.fulfill({status:403,contentType:'application/json',body:JSON.stringify({ok:false,code:'csrf_failed'})}):route.continue());
|
||||
await page.locator('#plan-form input[name="0.4"]').fill('550');await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.getByText(/Your sign-in session needs refreshing/).waitFor();assert.ok(page.url().includes('/app?plan='+saved.id));assert.equal((await db.query('SELECT COUNT(*) AS n FROM roast_plans')).rows[0].n,1);
|
||||
const draft=await page.evaluate(()=>JSON.parse(localStorage.getItem(Object.keys(localStorage).find(k=>k.startsWith('roastPlannerPlan.v2:')&&!k.endsWith(':last-user')))));assert.equal(draft.plan.fields['0.4'],'550');
|
||||
});
|
||||
test(
|
||||
"browser: rejected sync shows the cause and keeps the edited plan open",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db } = await session(t);
|
||||
const saved = await create(warningPlan());
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page.locator('#plan-form input[name="0.4"]').waitFor();
|
||||
await page.route("**/api/plans/" + saved.id, async (route) =>
|
||||
route.request().method() === "PUT"
|
||||
? route.fulfill({
|
||||
status: 403,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({ ok: false, code: "csrf_failed" }),
|
||||
})
|
||||
: route.continue(),
|
||||
);
|
||||
await page.locator('#plan-form input[name="0.4"]').fill("550");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.getByText(/Your sign-in session needs refreshing/).waitFor();
|
||||
assert.ok(page.url().includes("/app?plan=" + saved.id));
|
||||
assert.equal(
|
||||
(await db.query("SELECT COUNT(*) AS n FROM roast_plans")).rows[0].n,
|
||||
1,
|
||||
);
|
||||
const draft = await page.evaluate(() =>
|
||||
JSON.parse(
|
||||
localStorage.getItem(
|
||||
Object.keys(localStorage).find(
|
||||
(k) =>
|
||||
k.startsWith("roastPlannerPlan.v2:") && !k.endsWith(":last-user"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
assert.equal(draft.plan.fields["0.4"], "550");
|
||||
},
|
||||
);
|
||||
|
||||
test('browser: Clone opens an independent plan whose roast level can change', browserOptions, async t => {
|
||||
const {page,base,create,db,errors}=await session(t);const original=await create(warningPlan(),'Fruit original');await page.goto(base+'/plans');await page.getByRole('button',{name:'Clone Fruit original',exact:true}).click();await page.waitForURL('**/app?plan=*');await page.locator('#header-coffee-name').filter({hasText:'Fruit original (copy)'}).waitFor();
|
||||
const id=new URL(page.url()).searchParams.get('plan');assert.notEqual(id,original.id);await page.locator('#plan-form label').filter({has:page.locator('input[name="4.1"][value="medium"]')}).click();await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');await page.getByRole('link',{name:'Fruit original (copy)',exact:true}).waitFor();
|
||||
const rows=(await db.query('SELECT id,plan FROM roast_plans')).rows;assert.equal(rows.length,2);assert.equal(rows.find(r=>r.id===original.id).plan.fields['4.1'],'light');assert.equal(rows.find(r=>r.id===id).plan.fields['4.1'],'medium');assert.deepEqual(errors,[]);
|
||||
});
|
||||
test(
|
||||
"browser: Clone opens an independent plan whose roast level can change",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t);
|
||||
const original = await create(warningPlan(), "Fruit original");
|
||||
await page.goto(base + "/plans");
|
||||
await page
|
||||
.getByRole("button", { name: "Clone Fruit original", exact: true })
|
||||
.click();
|
||||
await page.waitForURL("**/app?plan=*");
|
||||
await page
|
||||
.locator("#header-coffee-name")
|
||||
.filter({ hasText: "Fruit original (copy)" })
|
||||
.waitFor();
|
||||
const id = new URL(page.url()).searchParams.get("plan");
|
||||
assert.notEqual(id, original.id);
|
||||
await page
|
||||
.locator("#plan-form label")
|
||||
.filter({ has: page.locator('input[name="4.1"][value="medium"]') })
|
||||
.click();
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page
|
||||
.getByRole("link", { name: "Fruit original (copy)", exact: true })
|
||||
.waitFor();
|
||||
const rows = (await db.query("SELECT id,plan FROM roast_plans")).rows;
|
||||
assert.equal(rows.length, 2);
|
||||
assert.equal(
|
||||
rows.find((r) => r.id === original.id).plan.fields["4.1"],
|
||||
"light",
|
||||
);
|
||||
assert.equal(rows.find((r) => r.id === id).plan.fields["4.1"], "medium");
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
|
||||
test('browser: source URL persists from manual entry and prefill, with undo and cloning', browserOptions, async t => {
|
||||
const {page,base,create,db,errors}=await session(t);const original=await create(warningPlan(),'Source test');await page.goto(base+'/app?plan='+original.id);
|
||||
await page.locator('#header-coffee-name').filter({hasText:'Source test'}).waitFor();const source=page.locator('#plan-form input[name="sourceUrl"]');await source.fill('https://example.com/manual-coffee');await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');await page.getByRole('link',{name:'Source test',exact:true}).click();await page.locator('#header-coffee-name').filter({hasText:'Source test'}).waitFor();await source.waitFor();assert.equal(await source.inputValue(),'https://example.com/manual-coffee');
|
||||
await page.route('**/api/prefill',route=>route.fulfill({status:200,contentType:'application/json',body:JSON.stringify({ok:true,fields:{},source:{finalUrl:'https://example.com/new-coffee'},warnings:[]})}));
|
||||
await page.locator('#nav-prefill').click();await page.locator('#prefill-url').fill('https://example.com/new-coffee');await page.locator('#prefill-go').click();await page.locator('#prefill-result').filter({hasText:'Applied 0 fields'}).waitFor();assert.equal(await source.inputValue(),'https://example.com/new-coffee');await page.locator('#prefill-undo').click();assert.equal(await source.inputValue(),'https://example.com/manual-coffee');await page.locator('#prefill-go').click();await page.locator('#prefill-result').filter({hasText:'Applied 0 fields'}).waitFor();await page.keyboard.press('Escape');
|
||||
await page.getByRole('link',{name:'▤ Your plans',exact:true}).click();await page.waitForURL('**/plans*');assert.equal((await db.query('SELECT plan FROM roast_plans WHERE id=$1',[original.id])).rows[0].plan.sourceUrl,'https://example.com/new-coffee');await page.getByRole('button',{name:'Clone Source test',exact:true}).click();await page.waitForURL('**/app?plan=*');await page.locator('#header-coffee-name').filter({hasText:'Source test (copy)'}).waitFor();await source.waitFor();assert.equal(await source.inputValue(),'https://example.com/new-coffee');assert.deepEqual(errors,[]);
|
||||
});
|
||||
test(
|
||||
"browser: source URL persists from manual entry and prefill, with undo and cloning",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t);
|
||||
const original = await create(warningPlan(), "Source test");
|
||||
await page.goto(base + "/app?plan=" + original.id);
|
||||
await page
|
||||
.locator("#header-coffee-name")
|
||||
.filter({ hasText: "Source test" })
|
||||
.waitFor();
|
||||
const source = page.locator('#plan-form input[name="sourceUrl"]');
|
||||
await source.fill("https://example.com/manual-coffee");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page.getByRole("link", { name: "Source test", exact: true }).click();
|
||||
await page
|
||||
.locator("#header-coffee-name")
|
||||
.filter({ hasText: "Source test" })
|
||||
.waitFor();
|
||||
await source.waitFor();
|
||||
assert.equal(
|
||||
await source.inputValue(),
|
||||
"https://example.com/manual-coffee",
|
||||
);
|
||||
await page.route("**/api/prefill", (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
ok: true,
|
||||
fields: {},
|
||||
source: { finalUrl: "https://example.com/new-coffee" },
|
||||
warnings: [],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
await page.locator("#nav-prefill").click();
|
||||
await page.locator("#prefill-url").fill("https://example.com/new-coffee");
|
||||
await page.locator("#prefill-go").click();
|
||||
await page
|
||||
.locator("#prefill-result")
|
||||
.filter({ hasText: "Applied 0 fields" })
|
||||
.waitFor();
|
||||
assert.equal(await source.inputValue(), "https://example.com/new-coffee");
|
||||
await page.locator("#prefill-undo").click();
|
||||
assert.equal(
|
||||
await source.inputValue(),
|
||||
"https://example.com/manual-coffee",
|
||||
);
|
||||
await page.locator("#prefill-go").click();
|
||||
await page
|
||||
.locator("#prefill-result")
|
||||
.filter({ hasText: "Applied 0 fields" })
|
||||
.waitFor();
|
||||
await page.keyboard.press("Escape");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
assert.equal(
|
||||
(
|
||||
await db.query("SELECT plan FROM roast_plans WHERE id=$1", [
|
||||
original.id,
|
||||
])
|
||||
).rows[0].plan.sourceUrl,
|
||||
"https://example.com/new-coffee",
|
||||
);
|
||||
await page
|
||||
.getByRole("button", { name: "Clone Source test", exact: true })
|
||||
.click();
|
||||
await page.waitForURL("**/app?plan=*");
|
||||
await page
|
||||
.locator("#header-coffee-name")
|
||||
.filter({ hasText: "Source test (copy)" })
|
||||
.waitFor();
|
||||
await source.waitFor();
|
||||
assert.equal(await source.inputValue(), "https://example.com/new-coffee");
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"browser: Bean Bro conversation persists; proposed changes require review and can be undone or cloned",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t, {
|
||||
runPlanChat: async () => ({
|
||||
reply: "Try ten extra seconds and compare sweetness.",
|
||||
model: "test:coach",
|
||||
suggestions: [
|
||||
{
|
||||
fields: { 4.4: "1:25" },
|
||||
reason: "Test development.",
|
||||
expected: "More sweetness.",
|
||||
evidenceRoastIds: [],
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
const saved = await create(warningPlan(), "Bro experiment");
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page
|
||||
.getByRole("button", { name: "Review this plan", exact: true })
|
||||
.click();
|
||||
await page
|
||||
.getByRole("button", { name: "Apply changes", exact: true })
|
||||
.waitFor();
|
||||
const override = page.locator('#plan-form [name="4.4"]');
|
||||
assert.equal(await override.inputValue(), "");
|
||||
await page
|
||||
.getByRole("button", { name: "Apply changes", exact: true })
|
||||
.click();
|
||||
assert.equal(await override.inputValue(), "1:25");
|
||||
await page
|
||||
.getByRole("button", { name: "Undo changes", exact: true })
|
||||
.click();
|
||||
assert.equal(await override.inputValue(), "");
|
||||
await page.getByRole("link", { name: "← Your plans", exact: true }).click();
|
||||
await page.waitForURL("**/plans*");
|
||||
await page
|
||||
.getByRole("link", { name: "Bro experiment", exact: true })
|
||||
.click();
|
||||
await page
|
||||
.locator("#chat-thread")
|
||||
.filter({ hasText: "Try ten extra seconds" })
|
||||
.waitFor();
|
||||
await page
|
||||
.getByRole("button", { name: "Create a variation", exact: true })
|
||||
.click();
|
||||
await page.waitForURL(
|
||||
(url) =>
|
||||
url.pathname === "/app" && url.searchParams.get("plan") !== saved.id,
|
||||
);
|
||||
await page
|
||||
.locator("#header-coffee-name")
|
||||
.filter({ hasText: "(copy)" })
|
||||
.waitFor();
|
||||
assert.equal(await override.inputValue(), "1:25");
|
||||
const original = (
|
||||
await db.query("SELECT plan FROM roast_plans WHERE id=$1", [saved.id])
|
||||
).rows[0].plan;
|
||||
assert.equal(original.fields["4.4"], "");
|
||||
assert.equal(original.assistant.messages.length, 2);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
test(
|
||||
"browser: Source URL has a full row and mobile Bean Bro is a dedicated tab",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, errors } = await session(t);
|
||||
const saved = await create(warningPlan());
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page.locator(".planner-layout").waitFor();
|
||||
const source = page.locator('#plan-form [name="sourceUrl"]'),
|
||||
coffee = page.locator('#plan-form [name="0.1"]');
|
||||
const desktop = await source.boundingBox(),
|
||||
coffeeBox = await coffee.boundingBox();
|
||||
assert.ok(desktop.width > 400);
|
||||
assert.ok(coffeeBox.width > 500);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.waitForTimeout(350);
|
||||
await page
|
||||
.getByRole("button", { name: "Ask Bean Bro", exact: true })
|
||||
.filter({ visible: true })
|
||||
.click();
|
||||
assert.equal(await page.locator("#panel-chat").isVisible(), true);
|
||||
assert.equal(await page.locator(".planner-main").isVisible(), false);
|
||||
await page.getByRole("button", { name: "Plan", exact: true }).click();
|
||||
await page.waitForTimeout(400);
|
||||
assert.equal(await page.locator(".planner-main").isVisible(), true);
|
||||
assert.ok((await source.boundingBox()).width > 280);
|
||||
assert.equal(
|
||||
await page.evaluate(() => document.body.scrollWidth <= innerWidth),
|
||||
true,
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
test(
|
||||
"browser: roast-day events survive an offline reload",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, errors } = await session(t);
|
||||
const saved = await create(warningPlan());
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page
|
||||
.getByRole("button", { name: "Roast day", exact: true })
|
||||
.waitFor();
|
||||
await page.evaluate(() => navigator.serviceWorker.ready);
|
||||
await page.waitForFunction(
|
||||
() => navigator.serviceWorker.controller !== null,
|
||||
);
|
||||
await page.context().setOffline(true);
|
||||
await page.getByRole("button", { name: "Roast day", exact: true }).click();
|
||||
await page
|
||||
.getByRole("button", { name: "Start roast", exact: true })
|
||||
.click();
|
||||
await page
|
||||
.getByRole("spinbutton", {
|
||||
name: "Charge actual Fahrenheit",
|
||||
exact: true,
|
||||
})
|
||||
.fill("345");
|
||||
await page
|
||||
.locator(".roast-event")
|
||||
.first()
|
||||
.getByRole("button", { name: "Mark event", exact: true })
|
||||
.click();
|
||||
await page
|
||||
.getByRole("button", { name: "Close roast day", exact: true })
|
||||
.click();
|
||||
await page.reload();
|
||||
await page.getByRole("button", { name: "Roast day", exact: true }).click();
|
||||
assert.equal(
|
||||
await page
|
||||
.getByRole("spinbutton", {
|
||||
name: "Charge actual Fahrenheit",
|
||||
exact: true,
|
||||
})
|
||||
.inputValue(),
|
||||
"345",
|
||||
);
|
||||
await page.context().setOffline(false);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
test(
|
||||
"browser: coffee history compares measured curves and quick tasting persists",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, db, errors } = await session(t);
|
||||
const saved = await create(warningPlan());
|
||||
const owner = (
|
||||
await db.query("SELECT user_id FROM roast_plans WHERE id=$1", [saved.id])
|
||||
).rows[0].user_id;
|
||||
let first;
|
||||
for (let i = 1; i <= 2; i++) {
|
||||
const r = (
|
||||
await db.query(
|
||||
"INSERT INTO actual_roasts(user_id,roast_plan_id,filename,original_content,parsed,evaluation_status) VALUES($1,$2,$3,'{}',$4,'failed') RETURNING id",
|
||||
[
|
||||
owner,
|
||||
saved.id,
|
||||
`test-${i}.alog`,
|
||||
{
|
||||
roast: { title: `Batch ${i}` },
|
||||
curve: [
|
||||
{ timeS: 0, tempC: 200 },
|
||||
{ timeS: 60, tempC: 90 },
|
||||
{ timeS: 500, tempC: 190 },
|
||||
],
|
||||
derived: { firstCrackS: 450, developmentS: 50, dropS: 500 },
|
||||
},
|
||||
],
|
||||
)
|
||||
).rows[0];
|
||||
first ??= r.id;
|
||||
}
|
||||
await page.goto(base + "/roasts?roast=" + first);
|
||||
await page
|
||||
.getByRole("heading", { name: "Quick taste", exact: true })
|
||||
.waitFor();
|
||||
await page
|
||||
.locator('#after-form [name="tasteBalance"]')
|
||||
.selectOption("Balanced");
|
||||
await page.locator('#after-form [name="sweetness"]').selectOption("4");
|
||||
await page.locator('#after-form [name="favorite"]').selectOption("yes");
|
||||
await page
|
||||
.getByRole("button", { name: "Save after-roast", exact: true })
|
||||
.click();
|
||||
await page.waitForFunction(
|
||||
() => document.querySelector("#after-save").disabled === false,
|
||||
);
|
||||
await page.goto(
|
||||
base +
|
||||
"/coffees?coffee=" +
|
||||
encodeURIComponent(
|
||||
"coffee:" + warningPlan().fields["0.1"].toLowerCase(),
|
||||
),
|
||||
);
|
||||
await page.getByRole("heading", { name: "Your favorite batch" }).waitFor();
|
||||
await page
|
||||
.getByRole("checkbox", { name: "Compare Batch 1", exact: true })
|
||||
.check();
|
||||
await page
|
||||
.getByRole("checkbox", { name: "Compare Batch 2", exact: true })
|
||||
.check();
|
||||
await page
|
||||
.getByRole("button", { name: "Compare selected batches" })
|
||||
.click();
|
||||
await page
|
||||
.getByRole("img", {
|
||||
name: "Measured roast curves, Fahrenheit versus time",
|
||||
})
|
||||
.waitFor();
|
||||
assert.equal(await page.locator(".comparison-chart polyline").count(), 2);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
test(
|
||||
"browser: finishing manual roast day opens a tasting record",
|
||||
browserOptions,
|
||||
async (t) => {
|
||||
const { page, base, create, errors } = await session(t);
|
||||
const p = warningPlan();
|
||||
p.roastDay = {
|
||||
startedAt: Date.now() - 600000,
|
||||
events: {
|
||||
charge: { timeS: 0, tempF: "345" },
|
||||
fc: { timeS: 500, tempF: "360" },
|
||||
drop: { timeS: 580, tempF: "375" },
|
||||
},
|
||||
};
|
||||
const saved = await create(p, "Manual fruit batch");
|
||||
await page.goto(base + "/app?plan=" + saved.id);
|
||||
await page.getByRole("button", { name: "Roast day", exact: true }).click();
|
||||
await page
|
||||
.getByRole("button", { name: "Finish & record tasting", exact: true })
|
||||
.click();
|
||||
await page.waitForURL("**/roasts?roast=*");
|
||||
await page
|
||||
.getByRole("heading", { name: "Quick taste", exact: true })
|
||||
.waitFor();
|
||||
await page.getByRole("link", { name: "Download manual record" }).waitFor();
|
||||
assert.equal(
|
||||
await page.locator("#detail-download-updated").isVisible(),
|
||||
false,
|
||||
);
|
||||
assert.deepEqual(errors, []);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user