fix(app): preserve multipart file values when creating example from r… (#8129)

This commit is contained in:
Pooja
2026-05-29 18:56:27 +05:30
committed by GitHub
parent 18761ee156
commit 7413465bb4
10 changed files with 215 additions and 68 deletions

View File

@@ -0,0 +1,5 @@
{
"version": "1",
"name": "collection",
"type": "collection"
}

View File

@@ -0,0 +1,15 @@
meta {
name: chip-tooltip
type: http
seq: 1
}
post {
url: https://api.example.com/upload
body: multipartForm
auth: none
}
body:multipart-form {
files: @file(alpha.txt)
}

View File

@@ -0,0 +1,10 @@
{
"collections": [
{
"path": "{{projectRoot}}/tests/request/multipart-form/fixtures/collection",
"securityConfig": {
"jsSandboxMode": "safe"
}
}
]
}

View File

@@ -0,0 +1,12 @@
{
"maximized": false,
"lastOpenedCollections": [
"{{projectRoot}}/tests/request/multipart-form/fixtures/collection"
],
"preferences": {
"onboarding": {
"hasLaunchedBefore": true,
"hasSeenWelcomeModal": true
}
}
}

View File

@@ -0,0 +1,54 @@
import { test, expect } from '../../../playwright';
import type { Locator } from '@playwright/test';
import { closeAllCollections } from '../../utils/page';
test.describe('Multipart Form - Chip Tooltip Swap', () => {
test.afterAll(async ({ pageWithUserData: page }) => {
await closeAllCollections(page);
});
test('tooltip swaps between file path and "Remove file"', async ({ pageWithUserData: page }) => {
await page.locator('#sidebar-collection-name').getByText('collection').click();
await page.locator('.collection-item-name').filter({ hasText: 'chip-tooltip' }).click();
const tooltip = page.locator('[role="tooltip"], .react-tooltip').filter({ visible: true });
const inlineChip = page.getByTestId('multipart-file-chip').first();
const summary = page.getByTestId('multipart-file-summary');
await expect(inlineChip.or(summary).first()).toBeVisible({ timeout: 15000 });
let nameTarget: Locator, removeBtn: Locator;
if (await summary.count()) {
await summary.click();
const row = page.getByTestId('multipart-file-overflow-row').first();
await expect(row).toBeVisible();
nameTarget = row.locator('.overflow-row-name');
removeBtn = row.getByTestId('multipart-file-overflow-remove');
} else {
nameTarget = inlineChip.locator('.file-chip-name');
removeBtn = inlineChip.getByTestId('multipart-file-chip-remove');
}
await test.step('Hover chip body → file path', async () => {
await nameTarget.hover();
await expect(tooltip.first()).toBeVisible({ timeout: 15000 });
await expect(tooltip.first()).toContainText('alpha.txt');
});
await test.step('Hover X → "Remove file"', async () => {
await removeBtn.hover();
await expect(tooltip.first()).toHaveText('Remove file');
});
await test.step('Hover back to chip body → path again', async () => {
await nameTarget.hover();
await expect(tooltip.first()).not.toHaveText('Remove file');
await expect(tooltip.first()).toContainText('alpha.txt');
});
await test.step('Only one tooltip visible at a time', async () => {
await removeBtn.hover();
await expect(tooltip).toHaveCount(1);
});
});
});

View File

@@ -6,7 +6,7 @@ meta {
post {
url: https://api.example.com/upload
body: multipart-form
body: multipartForm
auth: none
}

View File

@@ -17,11 +17,6 @@ test.describe('Response Example - Multipart Form File Chips', () => {
}
});
// `pageWithUserData` reuses the Electron app across tests in the same worker
// (it doesn't pass `closePrevious: true`), so we can't assume a clean DOM
// between tests. This helper is idempotent: it only toggles the chevron when
// the examples list isn't already expanded, so re-running it after a
// previous test leaves things in either state still works.
const openMultipartExample = async (page: Page) => {
await page.locator('#sidebar-collection-name').getByText('collection').click();
@@ -45,11 +40,6 @@ test.describe('Response Example - Multipart Form File Chips', () => {
});
await test.step('All three files are present', async () => {
// The cell can be in one of three layout modes (inline chips, `+N more`
// overflow, or a fully collapsed `N files` summary) depending on the
// value-column width. CI Linux runners often have a small display that
// pushes the cell into the collapsed mode, so we read both inline chips
// and any overflow-dropdown rows to cover every case.
const summary = page.getByTestId('multipart-file-summary');
const more = page.getByTestId('multipart-file-more');
const inlineNames = await page.getByTestId('multipart-file-chip').allTextContents();

View File

@@ -0,0 +1,59 @@
import { test, expect } from '../../playwright';
import fs from 'fs';
import path from 'path';
const fixturePath = path.join(__dirname, 'fixtures', 'collection', 'multipart-example.bru');
test.describe('Response Example - multipart files preserved when creating example from request', () => {
// Snapshot the fixture so we restore the exact working-tree state (including
// any uncommitted changes), not whatever HEAD has.
let originalFixture: string;
test.beforeAll(() => {
originalFixture = fs.readFileSync(fixturePath, 'utf8');
});
test.afterAll(() => {
fs.writeFileSync(fixturePath, originalFixture);
});
test('file chips render real names, not "[Circular]"', async ({ pageWithUserData: page }) => {
await test.step('Open the multipart request', async () => {
await page.locator('#sidebar-collection-name').getByText('collection').click();
await page.locator('.collection-item-name').filter({ hasText: 'multipart-example' }).click();
});
await test.step('Open the 3-dot menu and pick "Create Example"', async () => {
const requestRow = page.locator('.collection-item-name').filter({ hasText: 'multipart-example' });
await requestRow.hover();
await requestRow.locator('.menu-icon').click({ force: true });
await page.locator('[role="menuitem"][data-item-id="create-example"]').click();
});
await test.step('Fill the modal and submit', async () => {
await page.getByTestId('create-example-name-input').clear();
await page.getByTestId('create-example-name-input').fill('Created From Request');
await page.getByRole('button', { name: 'Create Example' }).click();
});
await test.step('Example tab opens with the right title', async () => {
const title = page.getByTestId('response-example-title');
await expect(title).toBeVisible();
await expect(title).toContainText('Created From Request');
});
await test.step('File chips show real names', async () => {
// Read whichever layout shows up: inline chips or the collapsed summary dropdown.
const chips = page.getByTestId('multipart-file-chip');
let names = await chips.allTextContents();
if (names.length === 0) {
await page.getByTestId('multipart-file-summary').click();
names = await page.getByTestId('multipart-file-overflow-row').allTextContents();
}
expect(names).toEqual(['alpha.txt', 'beta.txt', 'gamma.txt']);
expect(names).not.toContain('[Circular]');
});
});
});