fix(core): Support element refs when capturing browser secrets (no-changelog) (backport to release-candidate/2.29.x) (#33661)

Co-authored-by: Bernhard Wittmann <bernhard.wittmann@n8n.io>
This commit is contained in:
n8n-assistant[bot] 2026-07-06 12:43:21 +00:00 committed by GitHub
parent 34a8a71dfd
commit bfa8db7045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 95 additions and 2 deletions

View File

@ -0,0 +1,85 @@
import type { Page } from 'playwright-core';
import { PlaywrightAdapter } from './playwright';
import { configureLogger } from '../logger';
import type { ResolvedConfig } from '../types';
configureLogger({ level: 'silent' });
const config: ResolvedConfig = {
defaultBrowser: 'chrome',
browsers: new Map(),
adapter: 'playwright',
mode: 'local',
};
interface FakeLocator {
count: ReturnType<typeof vi.fn>;
inputValue: ReturnType<typeof vi.fn>;
innerText: ReturnType<typeof vi.fn>;
}
function adapterWithLocator(pageId: string, locator: FakeLocator): PlaywrightAdapter {
const adapter = new PlaywrightAdapter(config);
const page = {
locator: vi.fn().mockReturnValue(locator),
} as unknown as Page;
const pageStates = (adapter as unknown as { pageStates: Map<string, { page: Page }> }).pageStates;
pageStates.set(pageId, { page });
return adapter;
}
describe('PlaywrightAdapter.getElementValue', () => {
it('returns the live input value for a ref target', async () => {
const locator: FakeLocator = {
count: vi.fn().mockResolvedValue(1),
inputValue: vi.fn().mockResolvedValue('xoxb-signing-secret'),
innerText: vi.fn(),
};
const adapter = adapterWithLocator('p1', locator);
const value = await adapter.getElementValue('p1', { ref: 'e5' });
expect(value).toBe('xoxb-signing-secret');
expect(locator.inputValue).toHaveBeenCalled();
});
it('falls back to inner text when the element is not an input', async () => {
const locator: FakeLocator = {
count: vi.fn().mockResolvedValue(1),
inputValue: vi.fn().mockRejectedValue(new Error('Node is not an <input>')),
innerText: vi.fn().mockResolvedValue('secret-in-code-block'),
};
const adapter = adapterWithLocator('p1', locator);
const value = await adapter.getElementValue('p1', { ref: 'e5' });
expect(value).toBe('secret-in-code-block');
});
it('falls back to inner text when the input value is empty', async () => {
const locator: FakeLocator = {
count: vi.fn().mockResolvedValue(1),
inputValue: vi.fn().mockResolvedValue(''),
innerText: vi.fn().mockResolvedValue('text-content'),
};
const adapter = adapterWithLocator('p1', locator);
const value = await adapter.getElementValue('p1', { ref: 'e5' });
expect(value).toBe('text-content');
});
it('reads the value for a selector target', async () => {
const locator: FakeLocator = {
count: vi.fn().mockResolvedValue(1),
inputValue: vi.fn().mockResolvedValue('from-selector'),
innerText: vi.fn(),
};
const adapter = adapterWithLocator('p1', locator);
const value = await adapter.getElementValue('p1', { selector: '#token' });
expect(value).toBe('from-selector');
});
});

View File

@ -1039,8 +1039,16 @@ export class PlaywrightAdapter {
}
}
getElementValue(_pageId: string, _target: ElementTarget): never {
throw new Error('Not implemented');
async getElementValue(pageId: string, target: ElementTarget): Promise<string> {
const locator = await this.resolveLocator(pageId, target);
let value = '';
try {
value = await locator.inputValue();
} catch {
// Not an <input>/<textarea>/<select>.
}
if (value !== '') return value;
return await locator.innerText();
}
private async resolveLocator(pageId: string, target: ElementTarget): Promise<Locator> {