diff --git a/src/PuppeteerRunnerExtension.ts b/src/PuppeteerRunnerExtension.ts index e470b94c..27aa9724 100644 --- a/src/PuppeteerRunnerExtension.ts +++ b/src/PuppeteerRunnerExtension.ts @@ -457,15 +457,17 @@ async function waitForElement( if (result && (properties || attributes)) { result = await elementsHandle.evaluate( (elements, properties, attributes) => { - for (const element of elements) { - if (attributes) { + if (attributes) { + for (const element of elements) { for (const [name, value] of Object.entries(attributes)) { if (element.getAttribute(name) !== value) { return false; } } } - if (properties) { + } + if (properties) { + for (const element of elements) { if (!isDeepMatch(properties, element)) { return false; } @@ -473,7 +475,7 @@ async function waitForElement( } return true; - function isDeepMatch(a: unknown, b: unknown) { + function isDeepMatch(a: T, b: unknown): b is T { if (a === b) { return true; } diff --git a/src/SchemaUtils.ts b/src/SchemaUtils.ts index 6951da3d..7a467c95 100644 --- a/src/SchemaUtils.ts +++ b/src/SchemaUtils.ts @@ -178,6 +178,13 @@ function parseOptionalString(step: object, prop: string): string | undefined { return undefined; } +function parseOptionalBoolean(step: object, prop: string): boolean | undefined { + if (hasProperty(step, prop)) { + return parseBoolean(step, prop); + } + return undefined; +} + function parseString(step: object, prop: string): string { if (hasProperty(step, prop)) { const maybeString = step[prop]; @@ -424,11 +431,35 @@ function parseWaitForElementStep(step: object): WaitForElementStep { "WaitForElement step's operator is not one of '>=','==','<='" ); } + if (hasProperty(step, 'attributes')) { + if ( + !isObject(step.attributes) || + Object.values(step.attributes).some( + (attribute) => typeof attribute !== 'string' + ) + ) { + throw new Error( + "WaitForElement step's attribute is not a dictionary of strings" + ); + } + } + if (hasProperty(step, 'properties')) { + if (!isObject(step.properties)) { + throw new Error("WaitForElement step's attribute is not an object"); + } + } return { ...parseStepWithSelectors(StepType.WaitForElement, step), type: StepType.WaitForElement, operator: operator as '>=' | '==' | '<=' | undefined, count: parseOptionalNumber(step, 'count'), + visible: parseOptionalBoolean(step, 'visible'), + attributes: hasProperty(step, 'attributes') + ? (step.attributes as WaitForElementStep['attributes']) + : undefined, + properties: hasProperty(step, 'properties') + ? (step.properties as WaitForElementStep['properties']) + : undefined, }; } diff --git a/test/SchemaUtils.test.ts b/test/SchemaUtils.test.ts index cba34af4..31998c83 100644 --- a/test/SchemaUtils.test.ts +++ b/test/SchemaUtils.test.ts @@ -322,6 +322,9 @@ describe('SchemaUtils', () => { selectors: [['aria/Test']], operator: '==', count: 1, + properties: {}, + attributes: {}, + visible: true, }, ], }), @@ -333,6 +336,9 @@ describe('SchemaUtils', () => { selectors: [['aria/Test']], operator: '==', count: 1, + properties: {}, + attributes: {}, + visible: true, }, ], } @@ -762,6 +768,31 @@ describe('SchemaUtils', () => { }, error: 'Timeout is not between 1 and 30000 milliseconds', }, + { + input: { + title: 'test', + steps: [ + { + type: 'waitForElement', + attributes: { test: 5 }, + }, + ], + }, + error: + "WaitForElement step's attribute is not a dictionary of strings", + }, + { + input: { + title: 'test', + steps: [ + { + type: 'waitForElement', + properties: null, + }, + ], + }, + error: "WaitForElement step's attribute is not an object", + }, ]; for (const testCase of testCases) { assert.throws(() => parse(testCase.input), testCase.error); diff --git a/test/resources/benchmark.json b/test/resources/benchmark.json index e72ee349..9779c7b4 100644 --- a/test/resources/benchmark.json +++ b/test/resources/benchmark.json @@ -103,11 +103,11 @@ { "type": "waitForElement", "target": "main", - "selectors": ["button"], - "count": 2, + "selectors": ["#button"], + "count": 1, "visible": true, "properties": { - "value": "" + "id": "button" }, "attributes": { "id": "button"