n8n/packages/testing/playwright/pages/components/InlineExpressionEditor.ts
n8n-cat-bot[bot] f0164ebc5e
Some checks are pending
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.22.3) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.16.0) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
test: Route ADO-2372 expression-editor assertions through (#33132)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:46:47 +00:00

150 lines
4.5 KiB
TypeScript

import type { Locator, Page } from '@playwright/test';
import { locatorByIndex } from '../../utils/index-helper';
/**
* Page object for the inline expression editor surface.
*
* Cohesive cluster spanning three render targets:
* - in-NDV input root (`parameter-input-*` / `inline-expression-editor-input`)
* - teleported preview popover (`inline-expression-editor-output`)
* - teleported expression modal (`expression-modal-input` / `expression-modal-output`)
*
* The popover and modal are teleported to the page root, so they're resolved
* through the derived `Page` rather than the NDV container.
*
* @example
* // Inside a hosting page object
* readonly inlineExpressionEditor = new InlineExpressionEditor(this.container);
*
* // Inside a test
* await n8n.ndv.activateParameterExpressionEditor('value');
* await n8n.ndv.typeInExpressionEditor('{{ 1 + 1');
*/
export class InlineExpressionEditor {
private readonly page: Page;
constructor(private readonly root: Locator) {
this.page = root.page();
}
/**
* In-NDV inline editor input.
* @param parameterName - Scope to a specific parameter; otherwise picks any in-NDV input
*/
getInput(parameterName?: string): Locator {
if (parameterName) {
return this.getParameterInput(parameterName).getByTestId('inline-expression-editor-input');
}
return this.root.getByTestId('inline-expression-editor-input');
}
getContent(): Locator {
return this.getInput().locator('.cm-content');
}
/** CodeMirror rendered lines within the in-NDV editor content. */
getLines(): Locator {
return this.getContent().locator('.cm-line');
}
/** A single rendered CodeMirror line by zero-based index. */
getLine(index: number): Locator {
return this.getLines().nth(index);
}
/** Teleported preview popover (same test id as the output). */
getPreview(): Locator {
return this.page.getByTestId('inline-expression-editor-output');
}
getOutput(): Locator {
return this.page.getByTestId('inline-expression-editor-output');
}
getItemInput(): Locator {
return this.page.getByTestId('inline-expression-editor-item-input').locator('input');
}
getItemPrevButton(): Locator {
return this.page.getByTestId('inline-expression-editor-item-prev');
}
getItemNextButton(): Locator {
return this.page.getByTestId('inline-expression-editor-item-next');
}
async selectNextItem(): Promise<void> {
await this.getItemNextButton().click();
}
async selectPrevItem(): Promise<void> {
await this.getItemPrevButton().click();
}
/** Flip the parameter to expression mode via the inline options toggle. */
async activate(parameterName: string): Promise<void> {
const parameterInput = this.getParameterInput(parameterName);
await parameterInput.click();
await this.root
.getByTestId(`${parameterName}-parameter-input-options-container`)
.getByTestId('radio-button-expression')
.click();
}
async clear(parameterName?: string): Promise<void> {
const editor = this.getInput(parameterName);
await editor.click();
await this.page.keyboard.press('ControlOrMeta+A');
await this.page.keyboard.press('Delete');
}
async type(text: string, parameterName?: string): Promise<void> {
const editor = this.getInput(parameterName);
await editor.click();
await editor.type(text);
}
/** Open the teleported expression modal for a parameter. */
async openModal(parameterName: string): Promise<void> {
await this.activate(parameterName);
const parameter = this.getParameterInput(parameterName);
await parameter.click();
await parameter.getByTestId('expander').click();
await this.page.getByTestId('expression-modal-input').waitFor({ state: 'visible' });
}
getModalInput(): Locator {
return this.page.getByTestId('expression-modal-input').getByRole('textbox');
}
getModalOutput(): Locator {
return this.page.getByTestId('expression-modal-output');
}
async fillModalInput(text: string): Promise<void> {
const input = this.getModalInput();
await input.clear();
await input.click();
await input.fill(text);
}
/** Drop an intentionally invalid expression in to exercise error rendering. */
async setInvalid({
fieldName,
invalidExpression,
}: {
fieldName: string;
invalidExpression?: string;
}): Promise<void> {
await this.activate(fieldName);
const editor = this.getInput(fieldName);
await editor.click();
await this.page.keyboard.type(invalidExpression ?? '{{ =()');
}
private getParameterInput(parameterName: string, index?: number): Locator {
return locatorByIndex(this.root.getByTestId(`parameter-input-${parameterName}`), index);
}
}