From be41e60a1341462cc4ade48ca0a46174675664c9 Mon Sep 17 00:00:00 2001 From: Solant Date: Thu, 17 Oct 2024 18:48:22 +0200 Subject: [PATCH] feat: add new subject type, implement window and document query --- COMPATIBILITY.md | 6 +-- src/actions.ts | 63 +++++++++++++++++++++----- src/cy.ts | 10 ++++ tests/2-advanced-examples/window.cy.ts | 3 +- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index beecff9..5b1903f 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -13,7 +13,7 @@ | [.children()](https://docs.cypress.io/api/commands/children) | ✅ | - | [3](#3-timeouts) | | [.closest()](https://docs.cypress.io/api/commands/closest) | 🕓 | 🕓 | | | [.contains()](https://docs.cypress.io/api/commands/contains) | ⚠️ | ⚠️ | selectors are not implemented, `includeShadowDom` option is not implemented, [3](#3-timeouts) | -| [.document()](https://docs.cypress.io/api/commands/document) | 🕓 | 🕓 | | +| [.document()](https://docs.cypress.io/api/commands/document) | ✅ | - | [3](#3-timeouts) | | [.eq()](https://docs.cypress.io/api/commands/eq) | ✅ | - | [3](#3-timeouts) | | [.filter()](https://docs.cypress.io/api/commands/filter) | ✅ | - | [3](#3-timeouts) | | [.find()](https://docs.cypress.io/api/commands/find) | ✅ | ⚠️ | `includeShadowDom` option is not implemented, [3](#3-timeouts) | @@ -41,7 +41,7 @@ | [.siblings()](https://docs.cypress.io/api/commands/siblings) | ⚠️ | 🕓 | selector argument is not implemented, [3](#3-timeouts) | | [.title()](https://docs.cypress.io/api/commands/title) | ✅ | - | [3](#3-timeouts) | | [.url()](https://docs.cypress.io/api/commands/url) | ✅ | ⚠️ | `decode` option is not implemented, [3](#3-timeouts) | -| [.window()](https://docs.cypress.io/api/commands/window) | 🕓 | 🕓 | | +| [.window()](https://docs.cypress.io/api/commands/window) | ✅ | - | [3](#3-timeouts) | ## Assertions @@ -156,7 +156,7 @@ the target element. Thus, in some cases, playwright action with `force` flag ena ### 2. Assertions -`.should()` assertions are supported (some of them might be missing), but there are just too many of them for this +`.should()` assertions are supported (some of them are missing), but there are just too many of them for this list (the whole chai BDD list). `expect()` assertions are not supported because their chained API is trickier to reimplement and as they rely too much diff --git a/src/actions.ts b/src/actions.ts index 949c823..69f196f 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,4 +1,6 @@ -import { expect, Locator, Page } from '@playwright/test'; +import { + expect, JSHandle, Locator, Page, +} from '@playwright/test'; export type SpecialSelector = { modifier: 'first' } | { modifier: 'last' } | { modifier: 'contains', @@ -93,6 +95,9 @@ export type Action = AssertActions | { | 'port' | 'protocol' | 'search' +} | { + type: 'handle', + global: 'window' | 'document', }; let queue: Array = []; @@ -115,7 +120,10 @@ function resolveSelectorItem(parent: Locator | Page, selector: Selector[number]) } } -export type Subject = { type: 'locator', value: Locator } | { type: 'value', value: unknown }; +export type Subject = + { type: 'locator', value: Locator } + | { type: 'value', value: unknown } + | { type: 'handle', value: JSHandle }; export async function evaluateAction( page: Page, @@ -124,6 +132,16 @@ export async function evaluateAction( aliasMap: Record, ): Promise { switch (action.type) { + case 'handle': { + switch (action.global) { + case 'window': + return { type: 'handle', value: await page.evaluateHandle(() => window) }; + case 'document': + return { type: 'handle', value: await page.evaluateHandle(() => window.document) }; + default: + throw new Error('Unknown handle value'); + } + } case 'alias': { // eslint-disable-next-line no-param-reassign aliasMap[action.name] = subject; @@ -195,13 +213,30 @@ export async function evaluateAction( } else { expect(subject.value).toContain(action.value); } - } else if (action.negation) { - await expect(subject.value).not.toContainText(action.value); - } else { - await expect(subject.value).toContainText(action.value); } - break; + if (subject.type === 'locator') { + if (action.negation) { + await expect(subject.value).not.toContainText(action.value); + } else { + await expect(subject.value).toContainText(action.value); + } + break; + } + throw new Error('Handle subject is not implemented'); case 'property': + if (subject.type === 'handle') { + const result = await page.evaluate( + (ctx) => Object.prototype.hasOwnProperty.call(ctx.subject, ctx.property), + { subject: subject.value, property: action.value }, + ); + if (action.negation) { + expect(result).not.toBe(true); + } else { + expect(result).toBe(true); + } + break; + } + if (action.negation) { expect(subject.value).not.toHaveProperty(action.value); } else { @@ -229,13 +264,17 @@ export async function evaluateAction( expect(Object.keys(subject.value as any)).not.toHaveLength(0); break; } - } else if (action.negation) { - await expect(subject.value).not.toBeEmpty(); + } + if (subject.type === 'locator') { + if (action.negation) { + await expect(subject.value).not.toBeEmpty(); + break; + } else { + await expect(subject.value).toBeEmpty(); + } break; - } else { - await expect(subject.value).toBeEmpty(); } - break; + throw new Error('Handle subject is not implemented'); case 'equal': if (action.negation) { expect(subject.value).not.toBe(action.value); diff --git a/src/cy.ts b/src/cy.ts index 6bba752..f562053 100644 --- a/src/cy.ts +++ b/src/cy.ts @@ -163,6 +163,16 @@ class Cy { return this.selfOrChild(); } + window() { + pushQueue({ type: 'handle', global: 'window' }); + return this.selfOrChild(); + } + + document() { + pushQueue({ type: 'handle', global: 'document' }); + return this.selfOrChild(); + } + hash() { return this.location('hash'); } diff --git a/tests/2-advanced-examples/window.cy.ts b/tests/2-advanced-examples/window.cy.ts index 4c8ad01..db0f035 100644 --- a/tests/2-advanced-examples/window.cy.ts +++ b/tests/2-advanced-examples/window.cy.ts @@ -1,4 +1,5 @@ import { setup } from '../../src'; + setup(); context('Window', () => { @@ -6,12 +7,10 @@ context('Window', () => { cy.visit('https://example.cypress.io/commands/window') }) - /* it('cy.window() - get the global window object', () => { // https://on.cypress.io/window cy.window().should('have.property', 'top') }) - */ /* it('cy.document() - get the document object', () => {