fix: restore saved credentials when switching back to original auth mode (#7911)

This commit is contained in:
Pooja
2026-05-18 12:52:10 +05:30
committed by GitHub
parent 736c050dae
commit 55774a8258
4 changed files with 248 additions and 7 deletions

View File

@@ -1683,8 +1683,14 @@ export const collectionsSlice = createSlice({
if (!item.draft) {
item.draft = cloneDeep(item);
}
item.draft.request.auth = {};
item.draft.request.auth.mode = action.payload.mode;
const newMode = action.payload.mode;
const savedAuth = get(item, 'request.auth');
const savedMode = get(savedAuth, 'mode');
if (newMode === savedMode) {
item.draft.request.auth = cloneDeep(savedAuth);
} else {
item.draft.request.auth = { mode: newMode };
}
}
}
},
@@ -2113,8 +2119,14 @@ export const collectionsSlice = createSlice({
root: cloneDeep(collection.root)
};
}
set(collection, 'draft.root.request.auth', {});
set(collection, 'draft.root.request.auth.mode', action.payload.mode);
const newMode = action.payload.mode;
const savedAuth = get(collection, 'root.request.auth');
const savedMode = get(savedAuth, 'mode');
if (newMode === savedMode) {
set(collection, 'draft.root.request.auth', cloneDeep(savedAuth));
} else {
set(collection, 'draft.root.request.auth', { mode: newMode });
}
}
},
updateCollectionAuth: (state, action) => {
@@ -3322,8 +3334,14 @@ export const collectionsSlice = createSlice({
if (!folder.draft) {
folder.draft = cloneDeep(folder.root);
}
set(folder, 'draft.request.auth', {});
set(folder, 'draft.request.auth.mode', action.payload.mode);
const newMode = action.payload.mode;
const savedAuth = get(folder, 'root.request.auth');
const savedMode = get(savedAuth, 'mode');
if (newMode === savedMode) {
set(folder, 'draft.request.auth', cloneDeep(savedAuth));
} else {
set(folder, 'draft.request.auth', { mode: newMode });
}
}
},
streamDataReceived: (state, action) => {

View File

@@ -0,0 +1,66 @@
import { test, expect } from '../../playwright';
import {
closeAllCollections,
createCollection,
createRequest,
openRequest,
readField,
saveRequest,
selectAuthMode,
selectRequestPaneTab,
typeIntoField
} from '../utils/page';
type CollectionFormat = 'bru' | 'yml';
const runDraftIndicatorScenario = (format: CollectionFormat) => {
test(`(${format}) switching back to the saved auth mode hides the draft indicator`, async ({ page, createTmpDir }) => {
const collectionName = `auth-draft-indicator-${format}`;
const requestName = `request-${format}`;
await createCollection(page, collectionName, await createTmpDir(), format);
await createRequest(page, requestName, collectionName, { url: 'https://example.com/api' });
await openRequest(page, collectionName, requestName);
await selectRequestPaneTab(page, 'Auth');
const requestTab = page
.locator('.request-tab')
.filter({ has: page.locator('.tab-label', { hasText: requestName }) });
await test.step('Save Bearer with a token — draft indicator clears', async () => {
await selectAuthMode(page, 'Bearer Token');
await typeIntoField(page, 'Token', 'saved-bearer-token');
await saveRequest(page);
await expect(requestTab.locator('.close-icon')).toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
});
await test.step('Switching to Basic Auth without saving shows the draft indicator', async () => {
await selectAuthMode(page, 'Basic Auth');
await expect(requestTab.locator('.has-changes-icon')).toBeVisible();
await expect(requestTab.locator('.close-icon')).not.toBeVisible();
});
await test.step('Switching back to the saved Bearer mode without saving hides the draft indicator', async () => {
await selectAuthMode(page, 'Bearer Token');
// Saved token is restored
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
// Draft now deep-equals the saved state — indicator must be gone
await expect(requestTab.locator('.close-icon')).toBeVisible();
await expect(requestTab.locator('.has-changes-icon')).not.toBeVisible();
});
});
};
test.describe('Auth mode switch — draft indicator clears on return to saved mode', () => {
test.afterEach(async ({ page }) => {
await closeAllCollections(page);
});
runDraftIndicatorScenario('bru');
runDraftIndicatorScenario('yml');
});

View File

@@ -0,0 +1,101 @@
import { test, expect } from '../../playwright';
import {
closeAllCollections,
createCollection,
createRequest,
createFolder,
openRequest,
readField,
saveRequest,
selectAuthMode,
selectRequestPaneTab,
typeIntoField
} from '../utils/page';
test.describe('Auth mode switch preserves saved data', () => {
test.afterEach(async ({ page }) => {
await closeAllCollections(page);
});
test('Request: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
await createCollection(page, 'auth-mode-switch-req', await createTmpDir());
await createRequest(page, 'request-1', 'auth-mode-switch-req', { url: 'https://example.com/api' });
await openRequest(page, 'auth-mode-switch-req', 'request-1');
await selectRequestPaneTab(page, 'Auth');
await test.step('Save Bearer with a token', async () => {
await selectAuthMode(page, 'Bearer Token');
await typeIntoField(page, 'Token', 'saved-bearer-token');
await saveRequest(page);
});
await test.step('Bearer → Basic → Bearer restores the saved token (the bug fix)', async () => {
await selectAuthMode(page, 'Basic Auth');
await selectAuthMode(page, 'Bearer Token');
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
});
await test.step('Switching to a non-saved mode shows empty fields (no regression)', async () => {
await selectAuthMode(page, 'Basic Auth');
await expect.poll(() => readField(page, 'Username')).toBe('');
await expect.poll(() => readField(page, 'Password')).toBe('');
});
await test.step('Switching to a third unrelated mode also leaves fields empty', async () => {
// Bearer is the saved mode; Digest has never been touched.
await selectAuthMode(page, 'Digest Auth');
await expect.poll(() => readField(page, 'Username')).toBe('');
await expect.poll(() => readField(page, 'Password')).toBe('');
});
await test.step('Returning once more to Bearer still restores the saved token', async () => {
await selectAuthMode(page, 'Bearer Token');
await expect.poll(() => readField(page, 'Token')).toBe('saved-bearer-token');
});
});
test('Collection: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
await createCollection(page, 'auth-mode-switch-col', await createTmpDir());
// The collection settings tab opens automatically on creation.
await page.locator('.tab.auth').click();
await test.step('Save Bearer at the collection level', async () => {
await selectAuthMode(page, 'Bearer Token');
await typeIntoField(page, 'Token', 'collection-bearer-token');
await page.getByRole('button', { name: 'Save' }).click();
});
await test.step('Bearer → Basic → Bearer restores the saved collection token', async () => {
await selectAuthMode(page, 'Basic Auth');
await selectAuthMode(page, 'Bearer Token');
await expect.poll(() => readField(page, 'Token')).toBe('collection-bearer-token');
});
});
test('Folder: switching back to the saved mode restores its credentials', async ({ page, createTmpDir }) => {
await createCollection(page, 'auth-mode-switch-folder', await createTmpDir());
await createFolder(page, 'folder-1', 'auth-mode-switch-folder', true);
// Open the folder settings tab.
await page.locator('.collection-item-name').filter({ hasText: 'folder-1' }).dblclick();
await page.locator('.tab.auth').click();
await test.step('Save Bearer at the folder level', async () => {
await selectAuthMode(page, 'Bearer Token');
await typeIntoField(page, 'Token', 'folder-bearer-token');
await page.getByRole('button', { name: 'Save' }).click();
});
await test.step('Bearer → Basic → Bearer restores the saved folder token', async () => {
await selectAuthMode(page, 'Basic Auth');
await selectAuthMode(page, 'Bearer Token');
await expect.poll(() => readField(page, 'Token')).toBe('folder-bearer-token');
});
});
});

View File

@@ -80,7 +80,12 @@ const openCollection = async (page, collectionName: string) => {
*
* @returns void
*/
const createCollection = async (page, collectionName: string, collectionLocation: string) => {
const createCollection = async (
page,
collectionName: string,
collectionLocation: string,
format?: 'bru' | 'yml'
) => {
await test.step(`Create collection "${collectionName}"`, async () => {
await page.getByTestId('collections-header-add-menu').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Create collection' }).click();
@@ -123,6 +128,15 @@ const createCollection = async (page, collectionName: string, collectionLocation
await nameInput.fill(collectionName);
// Verify the name is correct before creating
await expect(nameInput).toHaveValue(collectionName, { timeout: 2000 });
if (format) {
await createCollectionModal.locator('.advanced-options .btn-advanced').click();
await page.locator('.tippy-box .dropdown-item').filter({ hasText: 'Show File Format' }).click();
const formatSelect = createCollectionModal.locator('#format');
await formatSelect.waitFor({ state: 'visible', timeout: 5000 });
await formatSelect.selectOption(format);
}
await createCollectionModal.getByRole('button', { name: 'Create', exact: true }).click();
// The modal closes via `onClose()` in the form's `onSubmit` success path,
@@ -1340,6 +1354,45 @@ const sendAndWaitForResponse = async (page: Page) => {
});
};
const fieldEditor = (page: Page, labelText: string) =>
page
.locator('label')
.filter({ hasText: new RegExp(`^${escapeRegExp(labelText)}$`) })
.locator('..')
.locator('.single-line-editor-wrapper .CodeMirror');
/**
* Open the auth mode dropdown and pick a mode by its visible label.
* @param page - The page object
* @param modeLabel - Dropdown item text (e.g. 'Bearer Token', 'Basic Auth')
*/
const selectAuthMode = async (page: Page, modeLabel: string) => {
await page.locator('.auth-mode-label').click();
await page.locator('.dropdown-item').filter({ hasText: modeLabel }).click();
};
/**
* Type into a single-line CodeMirror editor identified by its sibling label.
* @param page - The page object
* @param labelText - Exact label text next to the editor
* @param value - The text to type
*/
const typeIntoField = async (page: Page, labelText: string, value: string) => {
await fieldEditor(page, labelText).click();
await page.keyboard.type(value);
};
/**
* Read the current value of a single-line CodeMirror editor identified by its sibling label.
* @param page - The page object
* @param labelText - Exact label text next to the editor
*/
const readField = async (page: Page, labelText: string): Promise<string> => {
const editor = fieldEditor(page, labelText).first();
await editor.waitFor({ state: 'visible' });
return editor.evaluate((el: any) => (el as any).CodeMirror?.getValue() ?? '');
};
const createExampleFromSidebar = async (page: Page, requestName: string, exampleName: string, description: string = '') => {
const requestRow = page.locator('.collection-item-name').filter({ hasText: requestName }).first();
@@ -1422,6 +1475,9 @@ export {
addTestScript,
sendAndWaitForErrorCard,
sendAndWaitForResponse,
selectAuthMode,
typeIntoField,
readField,
createExampleFromSidebar,
openExampleFromSidebar
};