diff --git a/packages/playwright-core/src/server/injected/injectedScript.ts b/packages/playwright-core/src/server/injected/injectedScript.ts index cf0b51383a..5f6fd0aae7 100644 --- a/packages/playwright-core/src/server/injected/injectedScript.ts +++ b/packages/playwright-core/src/server/injected/injectedScript.ts @@ -1249,7 +1249,14 @@ export class InjectedScript { { // JS property if (expression === 'to.have.property') { - const received = (element as any)[options.expressionArg]; + let target = element; + const properties = options.expressionArg.split('.'); + for (let i = 0; i < properties.length - 1; i++) { + if (typeof target !== 'object' || !(properties[i] in target)) + return { received: undefined, matches: false }; + target = (target as any)[properties[i]]; + } + const received = (target as any)[properties[properties.length - 1]]; const matches = deepEquals(received, options.expectedValue); return { received, matches }; } diff --git a/tests/page/expect-misc.spec.ts b/tests/page/expect-misc.spec.ts index eacfb786e4..2e229aed01 100644 --- a/tests/page/expect-misc.spec.ts +++ b/tests/page/expect-misc.spec.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { stripAnsi } from '../config/utils'; import { test, expect } from './pageTest'; test.describe('toHaveCount', () => { @@ -165,6 +166,26 @@ test.describe('toHaveJSProperty', () => { const locator = page.locator('div'); await expect(locator).toHaveJSProperty('foo', null); }); + + test('pass nested', async ({ page }) => { + await page.setContent('
'); + await page.$eval('div', e => (e as any).foo = { nested: { a: 1, b: 'string', c: new Date(1627503992000) } }); + const locator = page.locator('div'); + await expect(locator).toHaveJSProperty('foo.nested', { a: 1, b: 'string', c: new Date(1627503992000) }); + await expect(locator).toHaveJSProperty('foo.nested.a', 1); + await expect(locator).toHaveJSProperty('foo.nested.b', 'string'); + await expect(locator).toHaveJSProperty('foo.nested.c', new Date(1627503992000)); + }); + + test('fail nested', async ({ page }) => { + await page.setContent(''); + await page.$eval('div', e => (e as any).foo = { nested: { a: 1, b: 'string', c: new Date(1627503992000) } }); + const locator = page.locator('div'); + const error1 = await expect(locator).toHaveJSProperty('foo.bar', { a: 1, b: 'string', c: new Date(1627503992001) }, { timeout: 1000 }).catch(e => e); + expect.soft(stripAnsi(error1.message)).toContain(`Received: undefined`); + const error2 = await expect(locator).toHaveJSProperty('foo.nested.a', 2, { timeout: 1000 }).catch(e => e); + expect.soft(stripAnsi(error2.message)).toContain(`Received: 1`); + }); }); test.describe('toHaveClass', () => {