From 04c7441c9e283c29d02a57a9faf808ae96b8e48e Mon Sep 17 00:00:00 2001 From: Vishnu Vardhan Date: Fri, 18 Sep 2026 17:51:47 +0530 Subject: [PATCH] fix: give a direct addValue its own action row --- .../direct-addvalue-gets-an-action-row.md | 9 ++++++ packages/service/tests/index.test.ts | 29 +++++++++++++++++++ packages/shared/src/action-mapping.ts | 6 +++- packages/shared/src/trace-actions.ts | 1 + packages/shared/tests/action-mapping.test.ts | 26 ++++++++++++++++- 5 files changed, 69 insertions(+), 2 deletions(-) create mode 100644 .changeset/direct-addvalue-gets-an-action-row.md diff --git a/.changeset/direct-addvalue-gets-an-action-row.md b/.changeset/direct-addvalue-gets-an-action-row.md new file mode 100644 index 00000000..ae3416d0 --- /dev/null +++ b/.changeset/direct-addvalue-gets-an-action-row.md @@ -0,0 +1,9 @@ +--- +"@wdio/devtools-service": patch +--- + +Give a direct `addValue` its own action row. It was excluded from the trace action vocabulary on the grounds that WDIO fires it inside `setValue`, where mapping it would double-count — but that assumed it only ever appears nested. A direct `addValue`, which is idiomatic on Appium, produced no action at all: the typing step was simply missing from the trace, and a spec doing click → `addValue` → `getText` exported two rows instead of three. + +The double-count the exclusion guarded against cannot happen. The service logs a command only when it matches the top of its own command stack, and that stack is pushed for top-level user commands alone — so the nested `addValue` never reaches the command log to be mapped. Neither the Selenium nor the Nightwatch adapter emits a command by that name at all; `addValue` is WDIO's, and Selenium's equivalent `sendKeys` appends too and has always mapped to a fill. It renders the same way. + +`clearValue` stays excluded: it has the same nesting story but no direct use that currently goes unrecorded. diff --git a/packages/service/tests/index.test.ts b/packages/service/tests/index.test.ts index aa22f87e..33f030dd 100644 --- a/packages/service/tests/index.test.ts +++ b/packages/service/tests/index.test.ts @@ -442,4 +442,33 @@ describe('DevtoolsService - Screencast Integration', () => { expect(ScreencastRecorder).toHaveBeenCalled() expect(mockScreencastRecorder.start).toHaveBeenCalledWith(mockBrowser) }) + + // #352 mapped `addValue` into ACTION_MAP. It was excluded because WDIO fires it + // INSIDE setValue, and mapping it was assumed to double-count — this is the + // guarantee that makes it safe: only a command matching the top of the stack is + // logged, so the nested call never reaches the command log to be mapped at all. + describe('a command nested inside another is not logged', () => { + it('logs setValue once, not setValue plus its inner addValue', async () => { + service.beforeCommand('setValue' as any, ['hello']) + // WDIO issues this from within setValue, below the top-level boundary. + service.beforeCommand('addValue' as any, ['hello']) + service.afterCommand('addValue' as any, ['hello'], undefined) + await service.afterCommand('setValue' as any, ['hello'], undefined) + + const logged = mockSessionCapturerInstance.afterCommand.mock.calls.map( + (call: unknown[]) => call[1] + ) + expect(logged).toEqual(['setValue']) + }) + + it('logs a direct addValue, which is the row #352 was missing', async () => { + service.beforeCommand('addValue' as any, ['hello']) + await service.afterCommand('addValue' as any, ['hello'], undefined) + + const logged = mockSessionCapturerInstance.afterCommand.mock.calls.map( + (call: unknown[]) => call[1] + ) + expect(logged).toEqual(['addValue']) + }) + }) }) diff --git a/packages/shared/src/action-mapping.ts b/packages/shared/src/action-mapping.ts index 2f739494..79602e49 100644 --- a/packages/shared/src/action-mapping.ts +++ b/packages/shared/src/action-mapping.ts @@ -15,7 +15,11 @@ export type { TraceAction } export { ASSERT_ACTION_CLASS, mapAssertCommand } // Excluded by design: -// clearValue / addValue — WDIO fires these inside setValue (duplicate events). +// clearValue — WDIO fires it inside setValue (duplicate events). `addValue` +// was excluded for the same reason and is now mapped: the nested call never +// reaches the command log (the service logs only a command matching the top +// of its stack), and a DIRECT addValue — idiomatic on Appium — was +// producing no action row at all. // executeScript — Selenium's `until` polling fires it ~50ms; also recurses // because @wdio/elements uses executeScript inside captureActionSnapshot. // WDIO's user-facing `execute`/`executeAsync` are still captured. diff --git a/packages/shared/src/trace-actions.ts b/packages/shared/src/trace-actions.ts index 4b3fc0e2..a79fcb48 100644 --- a/packages/shared/src/trace-actions.ts +++ b/packages/shared/src/trace-actions.ts @@ -57,6 +57,7 @@ export const ACTION_MAP: Record = { click: { class: 'Element', method: 'click' }, doubleClick: { class: 'Element', method: 'dblclick' }, setValue: { class: 'Element', method: 'fill' }, + addValue: { class: 'Element', method: 'fill' }, selectByVisibleText: { class: 'Element', method: 'selectOption' }, moveTo: { class: 'Element', method: 'hover' }, scrollIntoView: { class: 'Element', method: 'scrollIntoViewIfNeeded' }, diff --git a/packages/shared/tests/action-mapping.test.ts b/packages/shared/tests/action-mapping.test.ts index 6ed96007..e04c5729 100644 --- a/packages/shared/tests/action-mapping.test.ts +++ b/packages/shared/tests/action-mapping.test.ts @@ -69,7 +69,6 @@ describe('mapCommandToAction for read/query commands', () => { describe('mapCommandToAction still excludes noisy/internal commands', () => { it.each([ 'clearValue', - 'addValue', 'executeScript', '$', '$$', @@ -241,3 +240,28 @@ describe('assertion command names', () => { expect(mapCommandToAction('expect')).toBeNull() }) }) + +// A direct addValue is idiomatic on Appium, and it was excluded from the map on +// the assumption it only ever appears nested inside setValue. The typing step +// was then missing from the trace entirely — a click → addValue → getText spec +// produced two action rows. +describe('mapCommandToAction for addValue (#352)', () => { + it('maps it to a fill, like the other append-style typing commands', () => { + expect(mapCommandToAction('addValue')).toEqual({ + class: 'Element', + method: 'fill' + }) + }) + + it('renders the same way selenium sendKeys does', () => { + expect(mapCommandToAction('addValue')).toEqual( + mapCommandToAction('sendKeys') + ) + }) + + // Still excluded: unlike addValue it has no direct-use case that goes + // unrecorded, and WDIO fires it inside setValue. + it('leaves clearValue unmapped', () => { + expect(mapCommandToAction('clearValue')).toBeNull() + }) +})