Files
selfphoneng/scripts/e2e/call-transcripts.mjs
T
2026-10-21 19:10:02 -05:00

87 lines
3.6 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
// Spec 10 §3 on the Recordings page: a recording with a summary and a transcript (put straight
// into the database), Show transcript, and search by what was said. Runs after m1-t4.mjs. Usage:
// PG_CONTAINER=selfphoneng-postgres-1 PG_DB=<db> node scripts/e2e/call-transcripts.mjs \
// <baseUrl> <adminEmail> <adminPassword> <shotsDir>
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { launch } from './cdp.mjs';
const [base, email, pw, shotsDir] = process.argv.slice(2);
const psql = (sql) => {
const r = spawnSync(
'docker',
[
'exec',
process.env.PG_CONTAINER,
'psql',
'-U',
'selfphoneng',
'-d',
process.env.PG_DB,
'-c',
sql,
],
{ encoding: 'utf8' },
);
assert.equal(r.status, 0, r.stderr);
};
psql(`insert into recordings (linkedid, file_path, started_at, duration_ms, transcript, summary, ai_done_at) values
('e2e-t1', '2026/10/21/e2e-t1.wav', now() - interval '1 hour', 42000,
'[{"side":"caller","at":0.4,"text":"Hi, I never got the invoice."},{"side":"other","at":2.9,"text":"I''ll email it today."}]',
E'The caller never got their invoice; it will be emailed today.\\nAction items:\\n- Email the invoice today', now()),
('e2e-t2', '2026/10/21/e2e-t2.wav', now() - interval '2 hours', 30000, null, null, now())`);
psql(
`update recordings set ai_error = 'Recorded before call transcripts were on' where linkedid = 'e2e-t2'`,
);
const text = (s) => `document.body.innerText.includes(${JSON.stringify(s)})`;
const p = await launch({ shotsDir });
try {
await p.goto(`${base}/login`);
await p.waitFor(`!!document.querySelector('input[type=email]')`);
await p.fill('Email', email);
await p.fill('Password', pw);
await p.click('Sign in');
await p.waitFor(`location.pathname === '/admin'`, 'signed in');
await p.goto(`${base}/admin/recordings`);
await p.waitFor(text('Action items:'), 'summary');
await p.waitFor(text('No transcript: Recorded before call transcripts were on'), 'why not');
await p.click('Show transcript');
await p.waitFor(`!!document.querySelector('ol[aria-label="Transcript"]')`, 'transcript');
const lines = await p.evaluate(
`[...document.querySelectorAll('ol[aria-label="Transcript"] li')].map(l => l.innerText.replace(/\\s+/g, ' ')).join(' | ')`,
);
assert.equal(
lines,
"0:00 Caller Hi, I never got the invoice. | 0:02 Other side I'll email it today.",
);
await p.shot('ct-01-transcript');
console.log('recordings: summary, why one has none, the transcript in order');
await p.evaluate(`(() => {
const el = document.querySelector('input[aria-label="Search transcripts"]');
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(el, 'email it');
el.dispatchEvent(new Event('input', { bubbles: true }));
return true;
})()`);
await p.click('Search');
await p.waitFor(
`document.querySelectorAll('ul[aria-label="Recordings"] > li').length === 1 && ${text('Action items:')}`,
'one match',
);
await p.evaluate(`(() => {
const el = document.querySelector('input[aria-label="Search transcripts"]');
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(el, 'refund');
el.dispatchEvent(new Event('input', { bubbles: true }));
return true;
})()`);
await p.click('Search');
await p.waitFor(text('No call mentions that.'), 'no match');
await p.shot('ct-02-search');
console.log('recordings: search finds the call by what was said, and says when nothing does');
} finally {
await p.close();
}