mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-08-25 20:46:31 +00:00
test(e2e): settle editor typing, resources input and the stand login detector
- Route both the knowledge and template editor inputs through a shared typeIntoEditor helper (pressSequentially), instead of fill() on one and a documented ProseMirror-race workaround on the other. - Make the mkdir spec type a name distinct from the dialog default and pin it, so a broken input→payload binding no longer matches the default. - Derive the stand login-readiness locator from E2E_USER instead of a hardcoded admin@ (and a "flows" button that does not exist). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5803d5adc6
commit
06f53c72a0
@@ -0,0 +1,11 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
|
||||
// The markdown editor is a ProseMirror contenteditable: fill() mutates the DOM directly and
|
||||
// races the observer flush against the next action (e.g. a submit click), so the typed content
|
||||
// can fail to land in the model. Real key events apply synchronously — always type through this.
|
||||
export const typeIntoEditor = async (page: Page, name: string, text: string): Promise<void> => {
|
||||
const editor = page.getByRole('textbox', { name });
|
||||
|
||||
await editor.click();
|
||||
await editor.pressSequentially(text);
|
||||
};
|
||||
@@ -16,7 +16,9 @@ setup('authenticate', async ({ page }) => {
|
||||
// The migration-seeded admin carries password_change_required — the forced
|
||||
// change screen is skippable and must not block the suite.
|
||||
const skip = page.getByRole('button', { name: 'Skip for now' });
|
||||
const loggedIn = page.getByRole('button', { name: /admin@|flows/i });
|
||||
// The user menu is labelled with the signed-in address, so derive the "logged in" signal
|
||||
// from E2E_USER rather than a hardcoded admin@ (there is no button named "flows").
|
||||
const loggedIn = page.getByRole('button', { name: new RegExp(E2E_USER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i') });
|
||||
|
||||
await expect(skip.or(loggedIn).first()).toBeVisible({ timeout: 15_000 });
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
import { ResultType } from '@/graphql/types';
|
||||
|
||||
import { expect, test } from '../../fixtures/test.ts';
|
||||
import { typeIntoEditor } from '../../helpers/editor.ts';
|
||||
import { expectCleanPage } from '../../helpers/errors.ts';
|
||||
import { KNOWLEDGE_DOC, knowledgesCassette, makeKnowledge } from '../../mocks/cassettes/knowledges.ts';
|
||||
|
||||
@@ -28,7 +29,7 @@ test.describe('knowledges crud', { tag: '@crud' }, () => {
|
||||
test('blocks submit until the answer type is picked', async ({ page, pageErrorLog }) => {
|
||||
await openNewKnowledgeForm(page);
|
||||
await page.getByRole('textbox', { name: 'Question' }).fill('What is the E2E answer?');
|
||||
await page.getByRole('textbox', { name: 'Content' }).fill('E2E knowledge content');
|
||||
await typeIntoEditor(page, 'Content', 'E2E knowledge content');
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page.getByText('Answer type is required')).toBeVisible();
|
||||
@@ -66,7 +67,7 @@ test.describe('knowledges crud', { tag: '@crud' }, () => {
|
||||
test('creates a document and lands on its detail page', async ({ page, pageErrorLog }) => {
|
||||
await openNewKnowledgeForm(page);
|
||||
await page.getByRole('textbox', { name: 'Question' }).fill('What is the E2E answer?');
|
||||
await page.getByRole('textbox', { name: 'Content' }).fill('E2E knowledge content');
|
||||
await typeIntoEditor(page, 'Content', 'E2E knowledge content');
|
||||
await page.getByRole('combobox', { name: 'Answer type' }).click();
|
||||
await page.getByRole('option', { name: 'other' }).click();
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
@@ -23,7 +23,13 @@ test.describe('resources', { tag: '@coverage' }, () => {
|
||||
});
|
||||
|
||||
test.describe('mkdir', () => {
|
||||
const added: ResultOf<typeof ResourceAddedDocument> = { resourceAdded: CREATED_FOLDER };
|
||||
// Type a name distinct from the dialog's default so the request body proves the
|
||||
// typed value reached it — a value equal to the default would match even if the
|
||||
// input→payload binding were broken.
|
||||
const TYPED_PATH = 'e2e-typed-folder';
|
||||
const added: ResultOf<typeof ResourceAddedDocument> = {
|
||||
resourceAdded: { ...CREATED_FOLDER, name: TYPED_PATH, path: TYPED_PATH },
|
||||
};
|
||||
|
||||
test.use({
|
||||
cassette: resourcesCassette({
|
||||
@@ -31,7 +37,7 @@ test.describe('resources', { tag: '@coverage' }, () => {
|
||||
'POST /api/v1/resources/mkdir': [
|
||||
{
|
||||
body: { data: {}, status: 'success' },
|
||||
bodySubset: { path: 'new-folder' },
|
||||
bodySubset: { path: TYPED_PATH },
|
||||
setFlag: 'folder-created',
|
||||
},
|
||||
],
|
||||
@@ -50,10 +56,11 @@ test.describe('resources', { tag: '@coverage' }, () => {
|
||||
|
||||
await expect(dialog.getByRole('heading', { name: 'Create directory' })).toBeVisible();
|
||||
await expect(dialog.getByLabel('Path')).toHaveValue('new-folder');
|
||||
await dialog.getByLabel('Path').fill(TYPED_PATH);
|
||||
await dialog.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page.getByText('Directory created')).toBeVisible();
|
||||
await expect(page.getByRole('treeitem', { name: /new-folder/ })).toBeVisible();
|
||||
await expect(page.getByRole('treeitem', { name: new RegExp(TYPED_PATH) })).toBeVisible();
|
||||
expectCleanPage(pageErrorLog);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
import { ResultType } from '@/graphql/types';
|
||||
|
||||
import { expect, test } from '../../fixtures/test.ts';
|
||||
import { typeIntoEditor } from '../../helpers/editor.ts';
|
||||
import { expectCleanPage } from '../../helpers/errors.ts';
|
||||
import { makeTemplate, TEMPLATE_SEED, templatesCassette } from '../../mocks/cassettes/templates.ts';
|
||||
|
||||
@@ -55,13 +56,7 @@ test.describe('templates crud', { tag: '@crud' }, () => {
|
||||
await expect(page).toHaveURL(/\/templates\/new$/);
|
||||
|
||||
await page.getByLabel('Title').fill(NEW_TITLE);
|
||||
|
||||
// fill() mutates the contenteditable DOM directly and races ProseMirror's
|
||||
// observer flush against the submit click; real key events apply synchronously.
|
||||
const editor = page.getByRole('textbox', { name: 'Template content' });
|
||||
|
||||
await editor.click();
|
||||
await editor.pressSequentially(NEW_TEXT);
|
||||
await typeIntoEditor(page, 'Template content', NEW_TEXT);
|
||||
await page.getByRole('button', { name: 'Create' }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/templates$/);
|
||||
|
||||
Reference in New Issue
Block a user