feat(openapi): complete upload preview and UI evidence
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
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', 'Операции'));
|
||||
@@ -15,14 +25,20 @@ test('operations page shows demo catalog and filter works', async ({ page }) =>
|
||||
|
||||
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: /Импорт OpenAPI/i }).click();
|
||||
await page.getByRole('button', { name: localized('Import OpenAPI', 'Импорт OpenAPI') }).click();
|
||||
await expect(page.locator('#openapi-import-modal')).toBeVisible();
|
||||
await page.locator('#openapi-import-document').fill(`
|
||||
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}:
|
||||
@@ -76,8 +92,13 @@ paths:
|
||||
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();
|
||||
@@ -92,13 +113,13 @@ paths:
|
||||
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('Ответ');
|
||||
await expect(page.locator('#openapi-import-selection')).toContainText('Выбрано: 2');
|
||||
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('Выбрано: 1');
|
||||
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);
|
||||
@@ -106,15 +127,359 @@ paths:
|
||||
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('Результат импорта');
|
||||
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(/Описание инструмента слишком короткое/);
|
||||
await expect(page.locator('.openapi-import-result-row').filter({ hasText: operationId })).toContainText('Исправить в мастере');
|
||||
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 upload invalidates active draft creation 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 = [];
|
||||
await page.route('**/imports/openapi/*/create', async (route) => {
|
||||
await new Promise((resolve) => releases.push(resolve));
|
||||
try {
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ created: [{ name: 'stale draft', operation_id: 'op_stale', 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');
|
||||
await page.evaluate(() => {
|
||||
window.dispatchEvent(new StorageEvent('storage', { key: 'crank_lang', newValue: 'en' }));
|
||||
});
|
||||
releases.shift()();
|
||||
await expect(page.locator('#openapi-import-preview-panel')).toBeHidden();
|
||||
await expect(page.locator('#openapi-import-result')).toBeHidden();
|
||||
await expect(page.locator('#openapi-import-create')).toBeEnabled();
|
||||
|
||||
await beginApply('workspace-change.yaml');
|
||||
await page.evaluate(() => {
|
||||
window.dispatchEvent(new CustomEvent('crank:workspacechange', { detail: { id: 'workspace-after-apply' } }));
|
||||
});
|
||||
releases.shift()();
|
||||
await expect(page.locator('#openapi-import-preview-panel')).toBeHidden();
|
||||
await expect(page.locator('#openapi-import-result')).toBeHidden();
|
||||
await expect(page.locator('#openapi-import-file-name')).toContainText('No file selected');
|
||||
await expect(page.locator('#openapi-import-create')).toBeEnabled();
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user