const { test, expect } = require('@playwright/test'); const { login, localized, uniqueName } = require('./helpers'); async function dismissOnboardingIfOpen(page) { await page.locator('#crank-onboarding-panel').waitFor({ state: 'attached' }); await page.locator('[data-testid="onboarding-dismiss"]').waitFor({ state: 'attached' }); await page.evaluate(() => { const dismiss = document.querySelector('[data-testid="onboarding-dismiss"]'); if (dismiss) dismiss.click(); }); await expect(page.locator('#crank-onboarding-panel')).toBeHidden(); } test('operations page shows demo catalog and filter works', async ({ page }) => { await login(page); await expect(page.locator('.page-heading')).toHaveText(localized('Operations', 'Операции')); await expect(page.locator('.ws-switcher-trigger')).toBeVisible(); await expect(page.locator('#ws-dropdown')).toHaveCount(0); await expect(page.getByText(localized('Loading operations', 'Загрузка операций'))).toBeHidden(); expect(await page.locator('tbody tr').count()).toBeGreaterThanOrEqual(1); await page.getByPlaceholder(localized('Search operations', 'Поиск операций')).fill('frankfurter'); await expect(page.locator('tbody tr')).toHaveCount(1); await expect(page.locator('tbody tr').first()).toContainText(/frankfurter_latest_rate/i); }); test('operations page imports OpenAPI methods as drafts', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); const operationId = uniqueName('get_rates'); const statusOperationId = `${operationId}_status`; await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); await expect(page.locator('#openapi-import-modal')).toBeVisible(); const sourceCanary = 'openapi-source-canary-not-for-dom'; await page.locator('#openapi-import-file').setInputFiles({ name: 'demo-openapi.yaml', mimeType: 'application/octet-stream', buffer: Buffer.from(` openapi: 3.0.3 info: title: Demo Import API description: ${sourceCanary} servers: paths: /v1/rates/{date}: post: operationId: ${operationId} summary: Получить курсы валют description: Курсы. tags: [currency] parameters: - name: date in: path required: true schema: { type: string } - name: base in: query required: true schema: { type: string } - name: X-API-Version in: header required: false schema: { type: string } requestBody: required: true content: application/json: schema: type: object required: [symbols] properties: symbols: { type: string } responses: '200': description: OK content: application/json: schema: type: object properties: base: { type: string } /v1/status: get: operationId: ${statusOperationId} summary: Проверить статус tags: [service] responses: '200': description: OK content: application/json: schema: type: object properties: status: { type: string } `), }); const previewRequest = page.waitForRequest((request) => request.method() === 'POST' && request.url().includes('/imports/openapi/preview')); await page.locator('#openapi-import-preview').click(); const request = await previewRequest; expect(request.headers()['content-type']).toMatch(/^multipart\/form-data; boundary=/i); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); await expect(page.locator('.openapi-import-document-findings')).toContainText(/base URL/i); await expect(page.locator('.openapi-import-group').filter({ hasText: 'currency' })).toBeVisible(); const importedOperation = page.locator('.openapi-import-operation').filter({ hasText: 'Получить курсы валют' }); await expect(importedOperation).toBeVisible(); await expect(page.locator('.openapi-import-operation').filter({ hasText: 'Проверить статус' })).toBeVisible(); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('Path'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('date'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('Query'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('base'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('Header'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('X-API-Version'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('Body'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText('symbols'); await expect(importedOperation.locator('.openapi-import-mapping-preview')).toContainText(localized('Response', 'Ответ')); await expect(page.locator('#openapi-import-selection')).toContainText(localized('Selected: 2', 'Выбрано: 2')); await page.locator('#openapi-import-search').fill('status'); await expect(page.locator('.openapi-import-operation:visible')).toHaveCount(1); await expect(page.locator('.openapi-import-operation:visible').filter({ hasText: 'Проверить статус' })).toBeVisible(); await page.locator('#openapi-import-clear-visible').click(); await expect(page.locator('#openapi-import-selection')).toContainText(localized('Selected: 1', 'Выбрано: 1')); await page.locator('#openapi-import-search').fill(''); await page.locator('#openapi-import-method-filter').selectOption('POST'); await expect(page.locator('.openapi-import-operation:visible')).toHaveCount(1); await expect(page.locator('.openapi-import-operation:visible').filter({ hasText: 'Получить курсы валют' })).toBeVisible(); await page.locator('#openapi-import-method-filter').selectOption(''); await page.locator('#openapi-import-server-custom').fill('https://api.example.test'); await page.locator('#openapi-import-create').click(); await expect(page.locator('#openapi-import-result')).toContainText(localized('Import result', 'Результат импорта')); await expect(page.locator('.openapi-import-result-table')).toBeVisible(); await expect(page.locator('.openapi-import-result-row').filter({ hasText: operationId })).toContainText('POST /v1/rates/{date}'); await expect(page.locator('.openapi-import-result-row').filter({ hasText: operationId })).toContainText(localized('tool description is too short', 'Описание инструмента слишком короткое')); await expect(page.locator('.openapi-import-result-row').filter({ hasText: operationId })).toContainText(localized('Fix in wizard', 'Исправить в мастере')); await expect(page.locator('.openapi-import-primary-result a')).toHaveAttribute( 'href', /\/wizard\/\?mode=edit&operationId=op_/ ); await expect(page.locator('tbody')).toContainText(new RegExp(operationId, 'i')); await expect(page.locator('tbody')).not.toContainText(new RegExp(statusOperationId, 'i')); await expect(page.locator('body')).not.toContainText(sourceCanary); }); test('OpenAPI upload rejects invalid files locally and restores focus after Escape', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); const trigger = page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }); await trigger.focus(); await trigger.click(); await expect(page.locator('#openapi-import-file-select')).toBeFocused(); await page.keyboard.press('Shift+Tab'); await expect(page.locator('[data-openapi-close]').last()).toBeFocused(); await page.keyboard.press('Shift+Tab'); await expect(page.locator('#openapi-import-reset')).toBeFocused(); await page.locator('#openapi-import-file-select').focus(); await expect(page.locator('#openapi-import-status')).toHaveAttribute('role', 'status'); await expect(page.locator('#openapi-import-status')).toHaveAttribute('aria-live', 'polite'); let previewRequests = 0; page.on('request', (request) => { if (request.url().includes('/imports/openapi/preview')) previewRequests += 1; }); await page.locator('#openapi-import-preview').click(); expect(previewRequests).toBe(0); const longFileName = `${'a'.repeat(130)}.yaml`; await page.locator('#openapi-import-file').setInputFiles({ name: longFileName, mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await expect(page.locator('#openapi-import-file-name')).toContainText('… · 14 B'); await page.evaluate(() => window.setLang('en')); await expect(page.locator('#openapi-import-file-name')).toContainText('… · 14 B'); await expect(page.locator('#openapi-import-server-custom')).toHaveAttribute('aria-label', 'Custom Base URL'); await page.evaluate(() => window.setLang('ru')); await expect(page.locator('#openapi-import-file-name')).toContainText('… · 14 B'); await expect(page.locator('#openapi-import-server-custom')).toHaveAttribute('aria-label', 'Свой Base URL'); const invalidFiles = [ { file: { name: 'wrong-extension.txt', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3') }, message: localized('Choose one .yaml', 'Выберите один файл'), }, { file: { name: 'wrong-type.yaml', mimeType: 'text/plain', buffer: Buffer.from('openapi: 3.0.3') }, message: localized('matching type', 'подходящим типом'), }, { file: { name: 'empty.yaml', mimeType: 'application/yaml', buffer: Buffer.alloc(0) }, message: localized('file is empty', 'файл пуст'), }, { file: { name: 'too-large.yaml', mimeType: 'application/yaml', buffer: Buffer.alloc(256 * 1024 + 1, 'x') }, message: localized('larger than 256 KiB', 'больше 256 KiB'), }, ]; for (const invalid of invalidFiles) { await page.locator('#openapi-import-file').setInputFiles(invalid.file); await expect(page.locator('#openapi-import-status')).toContainText(invalid.message); await expect(page.locator('#openapi-import-status')).toBeFocused(); await page.locator('#openapi-import-preview').click(); expect(previewRequests).toBe(0); } await page.keyboard.press('Escape'); await expect(trigger).toBeFocused(); }); test('OpenAPI upload recovers from pagehide and a preview server error', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); let releasePreview; const pendingPreview = new Promise((resolve) => { releasePreview = resolve; }); await page.route('**/imports/openapi/preview', async (route) => { await pendingPreview; try { await route.fulfill({ status: 503, contentType: 'application/json', body: '{}' }); } catch (_error) { // pagehide aborts the active request before the delayed route settles. } }); await page.locator('#openapi-import-file').setInputFiles({ name: 'resume.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-status')).toContainText(localized('Uploading and parsing', 'Загружаю и разбираю')); await page.evaluate(() => window.dispatchEvent(new PageTransitionEvent('pagehide', { persisted: true }))); await expect(page.locator('#openapi-import-status')).toHaveText(''); await expect(page.locator('#openapi-import-cancel')).toBeHidden(); await page.evaluate(() => window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted: true }))); await expect(page.locator('#openapi-import-retry')).toBeHidden(); await expect(page.locator('#openapi-import-file-name')).toContainText(localized('No file selected', 'Файл не выбран')); await expect(page.locator('#openapi-import-status')).toContainText(localized('context changed', 'контекст изменился')); releasePreview(); }); test('OpenAPI apply cancellation preserves the import job for an idempotent retry', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); await page.route('**/imports/openapi/preview', async (route) => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ job_id: 'job_retry_same_authority', preview: { source: { servers: ['https://api.example.test'] }, findings: [], groups: [{ title: 'retry', operations: [{ key: 'get:/retry', method: 'GET', path: '/retry', suggested_name: 'preview_name', suggested_display_name: 'Retry', input_fields: 0, output_fields: 0, draft: { input_mapping: { rules: [] }, output_mapping: { rules: [] } }, findings: [], }], }], }, }), }); }); const requests = []; let releaseFirst; const firstRequest = new Promise((resolve) => { releaseFirst = resolve; }); await page.route('**/imports/openapi/*/create', async (route) => { requests.push({ url: route.request().url(), body: route.request().postData() }); if (requests.length === 1) await firstRequest; if (requests.length === 3) { await route.fulfill({ status: 409, contentType: 'application/json', body: JSON.stringify({ error: { code: 'import_job.application_mismatch', message: 'conflict' } }), }); return; } try { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ created: [{ operation_key: 'get:/retry', name: 'renamed_after_conflict', operation_id: 'op_retry', version: 1, }], skipped: [], findings: [], }), }); } catch (_error) { // The first request is deliberately aborted by the browser. } }); await page.locator('#openapi-import-file').setInputFiles({ name: 'retry.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); await page.locator('#openapi-import-create').click(); await expect.poll(() => requests.length).toBe(1); await page.locator('#openapi-import-cancel').click(); await expect(page.locator('#openapi-import-status')).toContainText(localized('may have completed', 'мог завершиться')); await expect(page.locator('#openapi-import-retry')).toBeVisible(); await page.locator('[data-openapi-close]').last().click(); await page.reload(); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); await expect(page.locator('#openapi-import-retry')).toBeVisible(); await page.locator('#openapi-import-retry').click(); await expect(page.locator('#openapi-import-result')).toContainText('GET /retry'); await expect.poll(() => requests.length).toBe(2); // Let the cancelled first attempt answer after the retry. Its stale response // must not overwrite or unlock the newer attempt's state. releaseFirst(); await page.waitForTimeout(50); await expect(page.locator('#openapi-import-result')).toContainText('GET /retry'); expect(requests[0].url).toContain('/imports/openapi/job_retry_same_authority/create'); expect(requests[1].url).toBe(requests[0].url); expect(requests[1].body).toBe(requests[0].body); // A received 4xx is a definitive non-commit outcome, unlike a transport // failure. It must release the wizard instead of trapping it in replay mode. await page.locator('#openapi-import-reset').click(); await page.locator('#openapi-import-file').setInputFiles({ name: 'definitive-4xx.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); await page.locator('#openapi-import-create').click(); await expect.poll(() => requests.length).toBe(3); await expect(page.locator('#openapi-import-file-select')).toBeEnabled(); await expect(page.locator('#openapi-import-reset')).toBeEnabled(); await expect(page.locator('#openapi-import-retry')).toBeVisible(); expect(await page.evaluate(() => sessionStorage.getItem('crank_openapi_apply_replay_v1'))).toBeNull(); }); test('OpenAPI apply preserves job authority after language or workspace changes', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); await page.route('**/imports/openapi/preview', async (route) => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ job_id: 'job_active_apply', preview: { source: { servers: ['https://api.example.test'] }, findings: [], groups: [{ title: 'active apply', operations: [{ key: 'get:/active', method: 'GET', path: '/active', suggested_name: 'active', suggested_display_name: 'Active', input_fields: 0, output_fields: 0, draft: { input_mapping: { rules: [] }, output_mapping: { rules: [] } }, findings: [], }], }], }, }), }); }); const releases = []; const applyRequests = []; await page.route('**/imports/openapi/*/create', async (route) => { applyRequests.push({ url: route.request().url(), body: route.request().postData() }); await new Promise((resolve) => releases.push(resolve)); try { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ created: [{ operation_key: 'get:/active', name: 'active', operation_id: 'op_active', version: 1 }], skipped: [], findings: [], }), }); } catch (_error) { // The invalidation aborts a request that must not update the modal afterwards. } }); async function beginApply(name) { await page.locator('#openapi-import-file').setInputFiles({ name, mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); await page.locator('#openapi-import-server-custom').fill('https://api.example.test'); await page.locator('#openapi-import-create').click(); await expect(page.locator('#openapi-import-cancel')).toBeVisible(); await expect.poll(() => releases.length).toBeGreaterThan(0); } await beginApply('language-change.yaml'); // Exercise the actual UI language switcher path: it updates storage, // re-renders translations and dispatches crank:langchange in one action. await page.evaluate(() => window.setLang('en')); await expect(page.locator('#openapi-import-status')).toContainText('may have completed'); await expect(page.locator('#openapi-import-retry')).toBeVisible(); await expect(page.locator('#openapi-import-file-select')).toBeDisabled(); releases.shift()(); await page.locator('#openapi-import-retry').click(); await expect.poll(() => releases.length).toBeGreaterThan(0); releases.shift()(); await expect(page.locator('#openapi-import-result')).toContainText('GET /active'); expect(applyRequests[1].url).toBe(applyRequests[0].url); expect(applyRequests[1].body).toBe(applyRequests[0].body); await page.locator('#openapi-import-reset').click(); await beginApply('workspace-change.yaml'); const workspaceRequest = applyRequests.at(-1); await page.evaluate(() => { window.dispatchEvent(new CustomEvent('crank:workspacechange', { detail: { id: 'workspace-after-apply' } })); }); await expect(page.locator('#openapi-import-status')).toContainText('may have completed'); await expect(page.locator('#openapi-import-retry')).toBeVisible(); await expect(page.locator('#openapi-import-file-select')).toBeDisabled(); releases.shift()(); await page.locator('#openapi-import-retry').click(); await expect.poll(() => releases.length).toBeGreaterThan(0); releases.shift()(); await expect(page.locator('#openapi-import-file-select')).toBeEnabled(); const workspaceReplay = applyRequests.at(-1); expect(workspaceReplay.url).toBe(workspaceRequest.url); expect(workspaceReplay.body).toBe(workspaceRequest.body); }); test('OpenAPI upload only renders the latest selected file and clears reset or close races', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); await page.route('**/imports/openapi/preview', async (route) => { const isFirst = route.request().postData().includes('first.yaml'); await new Promise((resolve) => setTimeout(resolve, isFirst ? 180 : 10)); try { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ job_id: isFirst ? 'job_first' : 'job_second', preview: { source: { servers: ['https://api.example.test'] }, findings: [], groups: [{ title: isFirst ? 'first-only' : 'second-only', operations: [{ key: isFirst ? 'get:/first' : 'get:/second', method: 'GET', path: isFirst ? '/first' : '/second', suggested_name: isFirst ? 'first' : 'second', suggested_display_name: isFirst ? 'First only' : 'Second only', input_fields: 0, output_fields: 0, draft: { input_mapping: { rules: [] }, output_mapping: { rules: [] } }, findings: [], }], }], }, }), }); } catch (_error) { // The browser intentionally aborts stale requests before this response settles. } }); await page.locator('#openapi-import-file').setInputFiles({ name: 'first.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); await page.locator('#openapi-import-server-custom').fill('https://stale.example.test'); await page.locator('#openapi-import-file').setInputFiles({ name: 'second.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await expect(page.locator('#openapi-import-server-custom')).toHaveValue(''); await expect(page.locator('#openapi-import-server option')).toHaveCount(0); await page.locator('#openapi-import-preview').click(); await expect(page.locator('.openapi-import-group')).toContainText('second-only'); await page.waitForTimeout(220); await expect(page.locator('.openapi-import-group')).not.toContainText('first-only'); await page.locator('#openapi-import-file').setInputFiles({ name: 'third.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await page.locator('#openapi-import-reset').click(); await page.waitForTimeout(220); await expect(page.locator('#openapi-import-preview-panel')).toBeHidden(); await expect(page.locator('#openapi-import-file-name')).toContainText(localized('No file selected', 'Файл не выбран')); await page.locator('#openapi-import-file').setInputFiles({ name: 'fourth.yaml', mimeType: 'application/yaml', buffer: Buffer.from('openapi: 3.0.3'), }); await page.locator('#openapi-import-preview').click(); await page.keyboard.press('Escape'); await page.waitForTimeout(220); await expect(page.locator('#openapi-import-modal')).toBeHidden(); }); test('OpenAPI upload ignores a stale failure and renders only correlation identifiers', async ({ page }) => { await login(page); await dismissOnboardingIfOpen(page); await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click(); const sourceCanary = 'do-not-render-openapi-source-canary'; await page.locator('#openapi-import-file').setInputFiles({ name: 'safe.yaml', mimeType: 'application/yaml', buffer: Buffer.from(`openapi: 3.0.3\ninfo: { title: Safe, description: ${sourceCanary} }\npaths: { /health: { get: { responses: { '200': { description: OK } } } } }\n`), }); let releaseStaleFailure; const staleFailure = new Promise((resolve) => { releaseStaleFailure = resolve; }); await page.route('**/imports/openapi/preview', async (route) => { await staleFailure; await route.fulfill({ status: 400, contentType: 'application/json', headers: { 'x-request-id': 'req_ui_evidence_1', 'x-trace-id': '0123456789abcdef0123456789abcdef', }, body: JSON.stringify({ error: { code: 'openapi_upload.invalid_document', message: 'Safe upload failure' } }), }); }); await page.locator('#openapi-import-preview').click(); await expect(page.locator('#openapi-import-cancel')).toBeVisible(); await page.locator('#openapi-import-cancel').click({ force: true }); releaseStaleFailure(); await page.waitForTimeout(100); await expect(page.locator('#openapi-import-status')).toContainText(localized('Request cancelled', 'Запрос отменён')); await expect(page.locator('body')).not.toContainText(sourceCanary); await page.unroute('**/imports/openapi/preview'); await page.route('**/imports/openapi/preview', async (route) => { await route.fulfill({ status: 400, contentType: 'application/json', headers: { 'x-request-id': 'req_ui_evidence_2', 'x-trace-id': 'abcdef0123456789abcdef0123456789', }, body: JSON.stringify({ error: { code: 'openapi_upload.invalid_document', message: 'Safe upload failure' } }), }); }); await page.locator('#openapi-import-retry').click(); await expect(page.locator('#openapi-import-status')).toContainText(localized('not a valid OpenAPI document', 'не является корректным документом OpenAPI')); await expect(page.locator('#openapi-import-status')).toBeFocused(); await expect(page.locator('#openapi-import-status')).toContainText('Request ID: req_ui_evidence_2'); await expect(page.locator('#openapi-import-status')).toContainText('Trace ID: abcdef0123456789abcdef0123456789'); await page.evaluate(() => window.setLang('en')); await expect(page.locator('#openapi-import-title')).toHaveText('Import OpenAPI'); await page.unroute('**/imports/openapi/preview'); await page.route('**/imports/openapi/preview', async (route) => { await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ job_id: 'job_ui_evidence', preview: { source: { servers: ['https://api.example.test'] }, findings: [], groups: [{ title: 'evidence', operations: [{ key: 'get:/health', method: 'GET', path: '/health', suggested_name: 'health', suggested_display_name: 'Health', input_fields: 0, output_fields: 0, draft: { input_mapping: { rules: [] }, output_mapping: { rules: [] } }, findings: [], }], }], }, }), }); }); await page.locator('#openapi-import-retry').click(); await expect(page.locator('#openapi-import-preview-panel')).toBeVisible(); let createRequests = 0; await page.route('**/imports/openapi/*/create', async (route) => { createRequests += 1; await new Promise((resolve) => setTimeout(resolve, 100)); await route.fulfill({ contentType: 'application/json', body: JSON.stringify({ created: [], skipped: [], findings: [] }) }); }); await page.locator('#openapi-import-create').click(); await page.locator('#openapi-import-create').evaluate((button) => button.click()); await expect(page.locator('#openapi-import-status')).toContainText('Created: 0; skipped: 0.'); expect(createRequests).toBe(1); });