Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion libraries/test/src/playwright/spec-login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ export async function softStep(name: string, fn: () => Promise<void>): Promise<v
catch (e: any) {
// `test.skip()` inside a step signals itself by throwing TestSkipError; recording
// that as a step error reports a deliberate skip as a failed test.
if (e?.constructor?.name === 'TestSkipError' || String(e?.message).startsWith('Test is skipped:'))
// A plain `Test is skipped:` throw is the suites' declared-skip idiom instead — every
// caller drops it from its end-of-test `realErrors` filter — so re-throwing it here
// escaped the step and turned the declared skip into a Playwright failure.
if (e?.constructor?.name === 'TestSkipError')
throw e;
stepErrors.push({step: name, error: e?.message ?? String(e)});
console.error(`[STEP FAILED] ${name}: ${e?.message ?? e}`);
Expand Down
6 changes: 4 additions & 2 deletions packages/Bio/playwright/bio-lifecycle-monomer-library.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,10 @@ test('Bio monomer_library source-class lifecycle: load → edit/save round-trip
} catch (e) {
return {dispatchErr: String(e).slice(0, 200), viewLabels};
}
// The first dispatch pays the monomer-library load: measured 12.3s cold on dev against
// 0.2s once warm, and CI is slower still. A 10s window lost the race outright.
let dialog: Element | null = null;
for (let i = 0; i < 50; i++) {
for (let i = 0; i < 200; i++) {
const candidates = Array.from(document.querySelectorAll('.d4-dialog'));
for (const d of candidates) {
if (d.querySelector('.monomer-lib-controls-form')) {
Expand Down Expand Up @@ -235,7 +237,7 @@ test('Bio monomer_library source-class lifecycle: load → edit/save round-trip
});
expect(result.dispatchErr, `dispatch error: ${result.dispatchErr}`).toBeNull();
expect(result.dialogOpened,
`expected dialog with .monomer-lib-controls-form within 10s; view labels: [${result.viewLabels.join(', ')}]`).toBe(true);
`expected dialog with .monomer-lib-controls-form within 40s; view labels: [${result.viewLabels.join(', ')}]`).toBe(true);
expect(result.dialogRowCount).toBeGreaterThanOrEqual(1);
expect(result.cataloguesAgree,
`view labels [${result.viewLabels.join(', ')}] disagree with dialog labels ` +
Expand Down
3 changes: 1 addition & 2 deletions packages/Bio/playwright/empty-input-row-viewers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const viewerCases: ViewerCase[] = [
for (const vc of viewerCases) {
test(`Bio ${vc.label} rejects empty current-row input with balloon`, async ({page}) => {
test.setTimeout(180_000);
test.fail(true, 'GROK-16111: Bio current-row viewers silently KNN on empty input, no rejection balloon — remove test.fail when fixed');
stepErrors.length = 0;
await loginToDatagrok(page);
await page.evaluate(async () => {
Expand Down Expand Up @@ -172,7 +171,7 @@ for (const vc of viewerCases) {
// Invariant 2 (.md): no silent zero-row result — the source table is not rewritten and the viewer reacts (docks or rejects), never a silent no-op.
expect(probe.rowCount, `${vc.label}: source table must not be silently rewritten on empty input`).toBe(baseRowCount);
expect(probe.docked || probe.balloonCount > 0, `${vc.label}: viewer must react on empty input (dock or reject), not silently no-op`).toBe(true);
// Invariant 1 (.md): empty current-row input must surface a rejection balloon. GROK-16111 keeps this failing today; test.fail() tracks the xfail and auto-flips to red when fixed.
// Invariant 1 (.md): empty current-row input must surface a rejection balloon. Fails on GROK-16111.
expect(probe.balloonCount, 'GROK-16111: empty current-row input must surface a rejection balloon').toBeGreaterThan(0);
});
finishSpec();
Expand Down
16 changes: 5 additions & 11 deletions packages/Peptides/playwright/info-panels.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,20 @@ test('Peptides — Info Panels', async ({page}) => {
await new Promise((r) => setTimeout(r, 5000));
try { await grok.functions.call('Peptides:initPeptides'); }
catch (e) { console.log('[note] Peptides:initPeptides pre-warm threw (non-fatal):', String(e)); }
const wiredDeadline = Date.now() + 30_000;
let detailsWired = false;
while (Date.now() < wiredDeadline) {
detailsWired = !!document.querySelector('.grok-prop-panel [name="pane-Details"]');
if (detailsWired) break;
await new Promise((r) => setTimeout(r, 250));
}
// Only that the panel is open: its panes belong to whatever grok.shell.o is, and
// right after addTableView that is the platform info object (Client/Server/Services/
// Plugins). Details arrives in step 2, with the column.
return {
rows: df.rowCount,
semType: df.col('AlignedSequence')?.semType,
detailsWired,
panelOpen: !!document.querySelector('.grok-prop-panel'),
activeViewType: (grok.shell.v as any)?.type ?? (grok.shell.v as any)?.constructor?.name,
};
}, datasetPath);
expect(result.rows).toBe(647);
expect(result.semType).toBe('Macromolecule');
expect(result.detailsWired).toBe(true);
expect(result.panelOpen).toBe(true);
await page.locator('[name="viewer-Grid"]').waitFor({timeout: 30_000});
await page.locator('.grok-prop-panel [name="pane-Details"]').waitFor({timeout: 30_000});
});
await softStep('Step 1: Verify amino acid coloring (cell.renderer === sequence)', async () => {
const renderer = await page.evaluate(() => {
Expand All @@ -63,7 +58,6 @@ test('Peptides — Info Panels', async ({page}) => {
});
await softStep('Step 2: Focus AlignedSequence column, wait for Context Panel rebuild', async () => {
await page.locator('[name="viewer-Grid"]').waitFor({timeout: 15_000});
await page.locator('.grok-prop-panel [name="pane-Details"]').waitFor({timeout: 15_000});
const probe = await page.evaluate(async () => {
const df = grok.shell.tv.dataFrame;
const col = df.col('AlignedSequence');
Expand Down
48 changes: 25 additions & 23 deletions packages/PowerPack/playwright/formula-refreshing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ sub_features_covered: [powerpack.dialogs, powerpack.dialogs.add-new-column, powe
// Load-bearing facts:
// - The CM6 EditorView is exposed at `cmContent.cmTile.view` (NOT cmDiv.cmView.view, which is always
// undefined and falls through to the brittle keyboard fallback). Same surface for dialog + panel widget.
// - The Formula info panel widget renders `.add-new-column-dialog-cm-div` (NOT -widget-cm-div): the
// constructor reads this.widget BEFORE assigning it, so the -dialog- class wins. Scope to .grok-prop-panel.
// - The CM host class depends on the host: `-widget-cm-div` in the info panel, `-dialog-cm-div` in
// the dialog (add-new-column.ts:211). Both were `-dialog-` until 979ac738a8 fixed the constructor
// to read its `widget` argument, so match either. Scope to .grok-prop-panel.
// - Widget mode does NOT call prepareForSeleniumTests(), so button-Add-New-Column---OK is unset; the
// widget's Apply button carries [name="button-Apply"] (text-content lookup as fallback).
// - The Formula accordion-pane header click TOGGLES (not idempotent) and persists across column switches;
Expand Down Expand Up @@ -437,6 +438,9 @@ async function waitForColumnPresent(page: any, columnName: string): Promise<void

// Edit a calc-column formula via the Formula info panel widget (PowerPack:formulaWidget spawns the same
// AddNewColumnDialog pre-bound to the column). Accordion-pane-click path with a direct-JS-API fallback.
// Matches the CM host in either place it can be rendered from — see the note at the top of the file.
const CM_HOST = '.add-new-column-widget-cm-div .cm-content, .add-new-column-dialog-cm-div .cm-content';

async function editFormulaViaInfoPanel(page: any, columnName: string, newFormula: string): Promise<void> {
// Set grok.shell.o to the column (same end state as a header click, deterministic under headless).
await page.evaluate((n: string) => {
Expand Down Expand Up @@ -464,15 +468,14 @@ async function editFormulaViaInfoPanel(page: any, columnName: string, newFormula
});
await page.waitForTimeout(500); // accordion expansion + widget re-render settle

// Wait for the widget's CM host. It renders .add-new-column-dialog-cm-div (the constructor reads
// this.widget before assigning it, so -dialog- wins); scope to .grok-prop-panel.
// Wait for the widget's CM host, scoped to .grok-prop-panel.
let widgetCmFound = false;
if (accordionPathWorked) {
for (let i = 0; i < 25; i++) {
widgetCmFound = await page.evaluate(() => {
widgetCmFound = await page.evaluate((sel: string) => {
const propPanel = document.querySelector('.grok-prop-panel');
return !!propPanel?.querySelector('.add-new-column-dialog-cm-div .cm-content');
});
return !!propPanel?.querySelector(sel);
}, CM_HOST);
if (widgetCmFound) break;
await page.waitForTimeout(200);
}
Expand All @@ -495,47 +498,47 @@ async function editFormulaViaInfoPanel(page: any, columnName: string, newFormula
void DG; // silence unused
}, columnName);
for (let i = 0; i < 25; i++) {
widgetCmFound = await page.evaluate(() => {
widgetCmFound = await page.evaluate((sel: string) => {
const propPanel = document.querySelector('.grok-prop-panel');
return !!propPanel?.querySelector('.add-new-column-dialog-cm-div .cm-content');
});
return !!propPanel?.querySelector(sel);
}, CM_HOST);
if (widgetCmFound) break;
await page.waitForTimeout(200);
}
}

if (!widgetCmFound)
throw new Error(`editFormulaViaInfoPanel: Formula widget CM host (.grok-prop-panel .add-new-column-dialog-cm-div .cm-content) did not render for column "${columnName}"`);
throw new Error(`editFormulaViaInfoPanel: Formula widget CM host (.grok-prop-panel ${CM_HOST}) ` +
`did not render for column "${columnName}"`);

// Dispatch the new formula via cmTile.view (same as composeAddNewColumn). Self-heal if the pane collapsed.
await page.evaluate(() => {
await page.evaluate((sel: string) => {
const pp = document.querySelector('.grok-prop-panel');
if (!pp) return;
const cm = pp.querySelector('.add-new-column-dialog-cm-div .cm-content') as HTMLElement | null;
const cm = pp.querySelector(sel) as HTMLElement | null;
if (cm && cm.offsetWidth === 0 && cm.offsetHeight === 0) {
const headers = Array.from(pp.querySelectorAll('.d4-accordion-pane-header, .d4-accordion-title'));
const formulaHeader = headers.find((h) => (h.textContent || '').trim() === 'Formula') as HTMLElement | undefined;
if (formulaHeader) formulaHeader.click();
}
});
}, CM_HOST);
await page.waitForTimeout(400);
const panelCm = page.locator(
'.grok-prop-panel .add-new-column-dialog-cm-div .cm-content').first();
CM_HOST.split(', ').map((sel) => `.grok-prop-panel ${sel}`).join(', ')).first();
await panelCm.waitFor({timeout: 15_000, state: 'visible'});
await panelCm.click({force: true});
await page.waitForTimeout(200);
let composed: {ok: boolean; doc?: string} = {ok: false};
for (let i = 0; i < 10; i++) {
composed = await page.evaluate((f: string) => {
composed = await page.evaluate(({f, sel}: {f: string, sel: string}) => {
const propPanel = document.querySelector('.grok-prop-panel');
const cmContent = propPanel?.querySelector(
'.add-new-column-dialog-cm-div .cm-content') as HTMLElement | null;
const cmContent = propPanel?.querySelector(sel) as HTMLElement | null;
if (!cmContent) return {ok: false};
const view = (cmContent as any).cmTile?.view ?? null;
if (!view) return {ok: false};
view.dispatch({changes: {from: 0, to: view.state.doc.length, insert: f}});
return {ok: true, doc: view.state.doc.toString()};
}, newFormula);
}, {f: newFormula, sel: CM_HOST});
if (composed.ok) break;
await page.waitForTimeout(200);
}
Expand All @@ -547,15 +550,14 @@ async function editFormulaViaInfoPanel(page: any, columnName: string, newFormula
await page.waitForTimeout(100);
await page.keyboard.type(newFormula, {delay: 30});
await page.waitForTimeout(200);
composed = await page.evaluate(() => {
composed = await page.evaluate((sel: string) => {
const propPanel = document.querySelector('.grok-prop-panel');
const cmContent = propPanel?.querySelector(
'.add-new-column-dialog-cm-div .cm-content') as HTMLElement | null;
const cmContent = propPanel?.querySelector(sel) as HTMLElement | null;
if (!cmContent) return {ok: false};
const view = (cmContent as any).cmTile?.view ?? null;
const doc = view ? view.state.doc.toString() : (cmContent.innerText || '');
return {ok: true, doc};
});
}, CM_HOST);
}
if (!composed.ok)
throw new Error('editFormulaViaInfoPanel: CodeMirror cmTile.view not exposed on in-panel widget even after keyboard fallback');
Expand Down
Loading