From 4f0829179c8601787da3c9ad207915e19f4b6a85 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Fri, 20 May 2022 11:57:17 -0400 Subject: [PATCH 01/11] wip --- core/src/components/refresher/refresher.tsx | 17 +- .../refresher/test/basic/index.html | 136 ++++++++-------- .../refresher/test/basic/refresher.e2e.ts | 63 ++++++++ .../refresher/test/scroll-target/index.html | 150 +++++++++--------- .../test/scroll-target/refresher.e2e.ts | 52 ++++++ core/src/utils/content/index.ts | 4 +- .../utils/test/playwright/page/utils/goto.ts | 4 +- .../playwright/playwright-declarations.ts | 29 +++- .../utils/test/playwright/playwright-page.ts | 53 +++++-- 9 files changed, 340 insertions(+), 168 deletions(-) create mode 100644 core/src/components/refresher/test/basic/refresher.e2e.ts create mode 100644 core/src/components/refresher/test/scroll-target/refresher.e2e.ts diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index e62342dec90..a8157183592 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -4,7 +4,12 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, readTas import { getIonMode } from '../../global/ionic-global'; import type { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier'; -import { findClosestIonContent, getScrollElement, printIonContentErrorMsg } from '../../utils/content'; +import { + getScrollElement, + ION_CONTENT_CLASS_SELECTOR, + ION_CONTENT_ELEMENT_SELECTOR, + printIonContentErrorMsg, +} from '../../utils/content'; import { clamp, getElementRoot, raf, transitionEndAsync } from '../../utils/helpers'; import { hapticImpact } from '../../utils/native/haptic'; @@ -436,13 +441,15 @@ export class Refresher implements ComponentInterface { return; } - const contentEl = findClosestIonContent(this.el); + const contentEl = this.el.closest(ION_CONTENT_ELEMENT_SELECTOR); if (!contentEl) { printIonContentErrorMsg(this.el); return; } - this.scrollEl = await getScrollElement(contentEl); + const customScrollTarget = contentEl.querySelector(ION_CONTENT_CLASS_SELECTOR); + + this.scrollEl = await getScrollElement(customScrollTarget ?? contentEl); /** * Query the host `ion-content` directly (if it is available), to use its @@ -452,9 +459,7 @@ export class Refresher implements ComponentInterface { * This makes it so that implementers do not need to re-create the background content * element and styles. */ - const backgroundContentHost = this.el.closest('ion-content') ?? contentEl; - - this.backgroundContentEl = getElementRoot(backgroundContentHost).querySelector( + this.backgroundContentEl = getElementRoot(contentEl ?? customScrollTarget).querySelector( '#background-content' ) as HTMLElement; diff --git a/core/src/components/refresher/test/basic/index.html b/core/src/components/refresher/test/basic/index.html index 51a6e3000fd..2a4b5eedcfb 100644 --- a/core/src/components/refresher/test/basic/index.html +++ b/core/src/components/refresher/test/basic/index.html @@ -1,83 +1,79 @@ - - - Refresher - Basic - - - - - - - - - - - - Pull To Refresh - - + + + Refresher - Basic + + + + + + + - - - - - + + + + + Pull To Refresh + + - - - - + + + + + - + - render(); - - diff --git a/core/src/components/refresher/test/basic/refresher.e2e.ts b/core/src/components/refresher/test/basic/refresher.e2e.ts new file mode 100644 index 00000000000..88efc9498eb --- /dev/null +++ b/core/src/components/refresher/test/basic/refresher.e2e.ts @@ -0,0 +1,63 @@ +import { expect } from '@playwright/test'; +import type { E2EPage } from '@utils/test/playwright'; +import { extendPageFixture, dragElementBy, test } from '@utils/test/playwright'; + +const pullToRefresh = async (page: E2EPage) => { + const target = page.locator('body'); + + const ev = await page.spyOnEvent('ionRefreshComplete'); + await dragElementBy(target, page, 0, 400); + await ev.next(); +} + +test.describe('refresher: basic', () => { + + let context: any; + let page: E2EPage; + + test.beforeEach(async ({ browser }) => { + context = await browser.newContext({ + recordVideo: { + dir: 'playwright-recordings/refresher/basic/' + } + }); + page = await extendPageFixture(await context.newPage()); + await page.goto('/src/components/refresher/test/basic', { waitUntil: 'networkidle' }); + }); + + test.afterEach(async () => { + await context.close(); + }) + + test.describe('legacy refresher', () => { + test('should load more items when performing a pull-to-refresh', async () => { + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page); + + expect(await items.count()).toBe(60); + }); + }); + + test.describe('native refresher', () => { + + test('should load more items when performing a pull-to-refresh', async () => { + const refresherContent = page.locator('ion-refresher-content'); + refresherContent.evaluateHandle((el: any) => { + // Resets the pullingIcon to enable the native refresher + el.pullingIcon = undefined; + }); + + await page.waitForChanges(); + + const items = page.locator('ion-item'); + expect(await items.count()).toBe(30); + + await pullToRefresh(page); + + expect(await items.count()).toBe(60); + }); + }); +}); diff --git a/core/src/components/refresher/test/scroll-target/index.html b/core/src/components/refresher/test/scroll-target/index.html index fe38d97e99f..1e5ff984162 100644 --- a/core/src/components/refresher/test/scroll-target/index.html +++ b/core/src/components/refresher/test/scroll-target/index.html @@ -1,93 +1,93 @@ - - - Refresher - Custom Scroll Target - - - - - - - - + contain: size style; + } + + #inner-scroll { + height: 100%; + overflow-y: auto; + } + + - - - - - Pull To Refresh - - + + + + + Pull To Refresh + + - - - - -
-
- -
+ + + + +
+
+
- - +
+
+ - + - render(); - - diff --git a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts new file mode 100644 index 00000000000..226234fbe8b --- /dev/null +++ b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts @@ -0,0 +1,52 @@ +import { expect } from '@playwright/test'; +import type { E2EPage } from '@utils/test/playwright'; +import { dragElementBy, test } from '@utils/test/playwright'; + +const pullToRefresh = async (page: E2EPage, selector: string) => { + const target = page.locator(selector); + const ev = await page.spyOnEvent('ionRefreshComplete'); + + await dragElementBy(target, page, 0, 300); + + await ev.next(); +} + +test.describe('refresher: custom scroll target', () => { + + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/refresher/test/scroll-target'); + }); + + test.describe('legacy refresher', () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page, '#inner-scroll'); + + expect(await items.count()).toBe(60); + }); + }); + + test.describe('native refresher', () => { + + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const refresherContent = page.locator('ion-refresher-content'); + refresherContent.evaluateHandle((el: any) => { + // Resets the pullingIcon to enable the native refresher + el.pullingIcon = undefined; + }); + + await page.waitForChanges(); + + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page, '#inner-scroll'); + + expect(await items.count()).toBe(60); + }); + }); +}); diff --git a/core/src/utils/content/index.ts b/core/src/utils/content/index.ts index 7e7a88bdf49..0d46e0fbfd2 100644 --- a/core/src/utils/content/index.ts +++ b/core/src/utils/content/index.ts @@ -2,8 +2,8 @@ import { componentOnReady } from '../helpers'; import { printRequiredElementError } from '../logging'; const ION_CONTENT_TAG_NAME = 'ION-CONTENT'; -const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content'; -const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host'; +export const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content'; +export const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host'; /** * Selector used for implementations reliant on `` for scroll event changes. * diff --git a/core/src/utils/test/playwright/page/utils/goto.ts b/core/src/utils/test/playwright/page/utils/goto.ts index e0270935d9e..f1667b259e9 100644 --- a/core/src/utils/test/playwright/page/utils/goto.ts +++ b/core/src/utils/test/playwright/page/utils/goto.ts @@ -7,7 +7,7 @@ import type { Page, TestInfo } from '@playwright/test'; * automatically waits for the Stencil components * to be hydrated before proceeding with the test. */ -export const goto = async (page: Page, url: string, testInfo: TestInfo, originalFn: typeof page.goto) => { +export const goto = async (page: Page, url: string, options: any, testInfo: TestInfo, originalFn: typeof page.goto) => { const { mode, rtl, _testing } = testInfo.project.metadata; const splitUrl = url.split('?'); @@ -38,7 +38,7 @@ export const goto = async (page: Page, url: string, testInfo: TestInfo, original const result = await Promise.all([ page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }), - originalFn(formattedUrl), + originalFn(formattedUrl, options), ]); return result[1]; diff --git a/core/src/utils/test/playwright/playwright-declarations.ts b/core/src/utils/test/playwright/playwright-declarations.ts index 11a93886d01..3f8d29c3196 100644 --- a/core/src/utils/test/playwright/playwright-declarations.ts +++ b/core/src/utils/test/playwright/playwright-declarations.ts @@ -27,7 +27,32 @@ export interface E2EPage extends Page { * @param url URL to navigate page to. The url should include scheme, e.g. `https://`. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. */ - goto: (url: string) => Promise; + goto: (url: string, options?: { + /** + * Referer header value. If provided it will take preference over the referer header value set by + * [page.setExtraHTTPHeaders(headers)](https://playwright.dev/docs/api/class-page#page-set-extra-http-headers). + */ + referer?: string; + + /** + * Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be + * changed by using the + * [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-navigation-timeout), + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout), + * [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + + /** + * When to consider operation succeeded, defaults to `load`. Events can be either: + * - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired. + * - `'load'` - consider operation to be finished when the `load` event is fired. + * - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms. + * - `'commit'` - consider operation to be finished when network response is received and the document started loading. + */ + waitUntil?: "load" | "domcontentloaded" | "networkidle" | "commit"; + }) => Promise; /** * Increases the size of the page viewport to match the `ion-content` contents. * Use this method when taking full-screen screenshots. @@ -50,9 +75,11 @@ export interface E2EPage extends Page { * never fires. * * Usage: + * ```ts * const ionChange = await page.spyOnEvent('ionChange'); * ... * await ionChange.next(); + * ``` */ spyOnEvent: (eventName: string) => Promise; _e2eEventsIds: number; diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index 2cfa61147c9..d59187885b3 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -29,22 +29,51 @@ type CustomFixtures = { page: E2EPage; }; -export const test = base.extend({ - page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { - const originalGoto = page.goto.bind(page); +/** + * Extends the base `page` test figure within Playwright. + * @param page The page to extend. + * @param testInfo The test info. + * @returns The modified playwright page with extended functionality. + */ +export async function extendPageFixture(page: E2EPage, testInfo?: TestInfo) { + const originalGoto = page.goto.bind(page); + // Overridden Playwright methods + + if (testInfo) { + page.goto = (url: string, options) => goToPage(page, url, options, testInfo, originalGoto); + } + page.setContent = (html: string) => setContent(page, html); - // Overridden Playwright methods - page.goto = (url: string) => goToPage(page, url, testInfo, originalGoto); - page.setContent = (html: string) => setContent(page, html); - // Custom Ionic methods + // Custom Ionic methods + + if (testInfo) { page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); - page.setIonViewport = () => setIonViewport(page); - page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs); - page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName); + } + page.setIonViewport = () => setIonViewport(page); + page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs); + page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName); + + // Custom event behavior + await initPageEvents(page); - // Custom event behavior - await initPageEvents(page); + return page; +} +export const test = base.extend({ + page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { + page = await extendPageFixture(page, testInfo); await use(page); }, + browser: async ({ browser }, use) => { + const originalNewPage = browser.newPage.bind(browser); + + browser.newPage = async (options: any) => { + const page = await originalNewPage(options); + return await extendPageFixture(page as E2EPage); + }; + + await use(browser); + + return browser; + } }); From 759e1d52981d962ae5539dabfea7ff827f09bd65 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 23 May 2022 13:31:33 -0400 Subject: [PATCH 02/11] test(refresher): e2e tests --- .../components/refresher/test/basic/e2e.ts | 51 ------- .../refresher/test/basic/refresher.e2e.ts | 32 +---- .../refresher/test/scroll-target/e2e.ts | 47 ------- .../test/scroll-target/refresher.e2e.ts | 12 +- .../src/components/refresher/test/spec/e2e.ts | 10 -- .../components/refresher/test/spec/index.html | 131 ------------------ .../components/refresher/test/test.utils.ts | 25 +++- 7 files changed, 28 insertions(+), 280 deletions(-) delete mode 100644 core/src/components/refresher/test/basic/e2e.ts delete mode 100644 core/src/components/refresher/test/scroll-target/e2e.ts delete mode 100644 core/src/components/refresher/test/spec/e2e.ts delete mode 100644 core/src/components/refresher/test/spec/index.html diff --git a/core/src/components/refresher/test/basic/e2e.ts b/core/src/components/refresher/test/basic/e2e.ts deleted file mode 100644 index 86d9d9117f6..00000000000 --- a/core/src/components/refresher/test/basic/e2e.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { newE2EPage } from '@stencil/core/testing'; - -import { pullToRefresh } from '../test.utils'; - -describe('refresher: basic', () => { - let page: E2EPage; - - beforeEach(async () => { - page = await newE2EPage({ - url: '/src/components/refresher/test/basic?ionic:_testing=true', - }); - }); - - it('should match existing visual screenshots', async () => { - const compare = await page.compareScreenshot(); - expect(compare).toMatchScreenshot(); - }); - - describe('legacy refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); - - describe('native refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const refresherContent = await page.$('ion-refresher-content'); - refresherContent.evaluate((el: any) => { - // Resets the pullingIcon to enable the native refresher - el.pullingIcon = undefined; - }); - - await page.waitForChanges(); - - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); -}); diff --git a/core/src/components/refresher/test/basic/refresher.e2e.ts b/core/src/components/refresher/test/basic/refresher.e2e.ts index 88efc9498eb..24f2ab264d3 100644 --- a/core/src/components/refresher/test/basic/refresher.e2e.ts +++ b/core/src/components/refresher/test/basic/refresher.e2e.ts @@ -1,36 +1,16 @@ import { expect } from '@playwright/test'; -import type { E2EPage } from '@utils/test/playwright'; -import { extendPageFixture, dragElementBy, test } from '@utils/test/playwright'; +import { test } from '@utils/test/playwright'; -const pullToRefresh = async (page: E2EPage) => { - const target = page.locator('body'); - - const ev = await page.spyOnEvent('ionRefreshComplete'); - await dragElementBy(target, page, 0, 400); - await ev.next(); -} +import { pullToRefresh } from '../test.utils'; test.describe('refresher: basic', () => { - let context: any; - let page: E2EPage; - - test.beforeEach(async ({ browser }) => { - context = await browser.newContext({ - recordVideo: { - dir: 'playwright-recordings/refresher/basic/' - } - }); - page = await extendPageFixture(await context.newPage()); - await page.goto('/src/components/refresher/test/basic', { waitUntil: 'networkidle' }); + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/refresher/test/basic'); }); - test.afterEach(async () => { - await context.close(); - }) - test.describe('legacy refresher', () => { - test('should load more items when performing a pull-to-refresh', async () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { const items = page.locator('ion-item'); expect(await items.count()).toBe(30); @@ -43,7 +23,7 @@ test.describe('refresher: basic', () => { test.describe('native refresher', () => { - test('should load more items when performing a pull-to-refresh', async () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { const refresherContent = page.locator('ion-refresher-content'); refresherContent.evaluateHandle((el: any) => { // Resets the pullingIcon to enable the native refresher diff --git a/core/src/components/refresher/test/scroll-target/e2e.ts b/core/src/components/refresher/test/scroll-target/e2e.ts deleted file mode 100644 index 1920e75924d..00000000000 --- a/core/src/components/refresher/test/scroll-target/e2e.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { newE2EPage } from '@stencil/core/testing'; - -import { pullToRefresh } from '../test.utils'; - -// TODO(FW-1134) Re-write these tests so that they test correct functionality. -describe.skip('refresher: custom scroll target', () => { - let page: E2EPage; - - beforeEach(async () => { - page = await newE2EPage({ - url: '/src/components/refresher/test/scroll-target?ionic:_testing=true', - }); - }); - - describe('legacy refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); - - describe('native refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const refresherContent = await page.$('ion-refresher-content'); - refresherContent.evaluate((el: any) => { - // Resets the pullingIcon to enable the native refresher - el.pullingIcon = undefined; - }); - - await page.waitForChanges(); - - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); -}); diff --git a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts index 226234fbe8b..14c3673b093 100644 --- a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts +++ b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts @@ -1,15 +1,7 @@ import { expect } from '@playwright/test'; -import type { E2EPage } from '@utils/test/playwright'; -import { dragElementBy, test } from '@utils/test/playwright'; +import { test } from '@utils/test/playwright'; -const pullToRefresh = async (page: E2EPage, selector: string) => { - const target = page.locator(selector); - const ev = await page.spyOnEvent('ionRefreshComplete'); - - await dragElementBy(target, page, 0, 300); - - await ev.next(); -} +import { pullToRefresh } from '../test.utils'; test.describe('refresher: custom scroll target', () => { diff --git a/core/src/components/refresher/test/spec/e2e.ts b/core/src/components/refresher/test/spec/e2e.ts deleted file mode 100644 index 5171c5281ae..00000000000 --- a/core/src/components/refresher/test/spec/e2e.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { newE2EPage } from '@stencil/core/testing'; - -test('refresher: spec', async () => { - const page = await newE2EPage({ - url: '/src/components/refresher/test/spec?ionic:_testing=true', - }); - - const compare = await page.compareScreenshot(); - expect(compare).toMatchScreenshot(); -}); diff --git a/core/src/components/refresher/test/spec/index.html b/core/src/components/refresher/test/spec/index.html deleted file mode 100644 index 51e3e708b3c..00000000000 --- a/core/src/components/refresher/test/spec/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - Refresher - Spec - - - - - - - - - - - - - - - - All Inboxes - - Edit - - - - - - - - - - - - All Inboxes - - - - - - - - - - - - - - - - - - - diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 7d0732aeb53..ebb048fbeae 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -1,5 +1,4 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { dragElementBy } from '@utils/test'; +import type { E2EPage } from "@utils/test/playwright"; /** * Emulates a pull-to-refresh drag gesture (pulls down and releases). @@ -12,10 +11,26 @@ import { dragElementBy } from '@utils/test'; * @param selector The element selector to center the drag gesture on. Defaults to `body`. */ const pullToRefresh = async (page: E2EPage, selector = 'body') => { - const target = (await page.$(selector))!; + const target = await page.locator(selector); - await dragElementBy(target, page, 0, 200); - const ev = await page.spyOnEvent('ionRefreshComplete', 'document'); + const ev = await page.spyOnEvent('ionRefreshComplete'); + const boundingBox = await target.boundingBox(); + + if (!boundingBox) { + return; + } + + const startX = boundingBox.x + boundingBox.width / 2; + const startY = boundingBox.y + boundingBox.height / 2; + + await page.mouse.move(startX, startY); + await page.mouse.down(); + + for (let i = 0; i < 400; i += 50) { + await page.mouse.move(startX, startY + i); + } + + await page.mouse.up(); await ev.next(); }; From 5b2b9303ff2166c538e063c2294397b71c6bb4c0 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 23 May 2022 13:57:00 -0400 Subject: [PATCH 03/11] chore(): clean-up --- core/src/components/refresher/test/test.utils.ts | 2 +- core/src/utils/test/playwright/playwright-page.ts | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index ebb048fbeae..5aba74f72cf 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -11,7 +11,7 @@ import type { E2EPage } from "@utils/test/playwright"; * @param selector The element selector to center the drag gesture on. Defaults to `body`. */ const pullToRefresh = async (page: E2EPage, selector = 'body') => { - const target = await page.locator(selector); + const target = page.locator(selector); const ev = await page.spyOnEvent('ionRefreshComplete'); const boundingBox = await target.boundingBox(); diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index d59187885b3..18778c63c41 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -63,17 +63,5 @@ export const test = base.extend({ page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { page = await extendPageFixture(page, testInfo); await use(page); - }, - browser: async ({ browser }, use) => { - const originalNewPage = browser.newPage.bind(browser); - - browser.newPage = async (options: any) => { - const page = await originalNewPage(options); - return await extendPageFixture(page as E2EPage); - }; - - await use(browser); - - return browser; } }); From b4cb4e44490f54ee15ca32ac017e06e7056f5d98 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 23 May 2022 14:10:33 -0400 Subject: [PATCH 04/11] chore(): lint --- .../refresher/test/basic/index.html | 136 ++++++++-------- .../refresher/test/basic/refresher.e2e.ts | 2 - .../refresher/test/scroll-target/index.html | 150 +++++++++--------- .../test/scroll-target/refresher.e2e.ts | 2 - .../components/refresher/test/test.utils.ts | 2 +- .../playwright/playwright-declarations.ts | 51 +++--- .../utils/test/playwright/playwright-page.ts | 2 +- 7 files changed, 174 insertions(+), 171 deletions(-) diff --git a/core/src/components/refresher/test/basic/index.html b/core/src/components/refresher/test/basic/index.html index 2a4b5eedcfb..60982df8670 100644 --- a/core/src/components/refresher/test/basic/index.html +++ b/core/src/components/refresher/test/basic/index.html @@ -1,79 +1,83 @@ + + + Refresher - Basic + + + + + + + - - - Refresher - Basic - - - - - - - + + + + + Pull To Refresh + + - - - - - Pull To Refresh - - + + + + + - - - - - + + + + - - - - - - - + function getAsyncData() { + // async return mock data + return new Promise((resolve) => { + setTimeout(() => { + let data = []; + for (var i = 0; i < 30; i++) { + data.push(i); + } + resolve(data); + }, 500); + }); + } + render(); + + diff --git a/core/src/components/refresher/test/basic/refresher.e2e.ts b/core/src/components/refresher/test/basic/refresher.e2e.ts index 24f2ab264d3..c67fca972ae 100644 --- a/core/src/components/refresher/test/basic/refresher.e2e.ts +++ b/core/src/components/refresher/test/basic/refresher.e2e.ts @@ -4,7 +4,6 @@ import { test } from '@utils/test/playwright'; import { pullToRefresh } from '../test.utils'; test.describe('refresher: basic', () => { - test.beforeEach(async ({ page }) => { await page.goto('/src/components/refresher/test/basic'); }); @@ -22,7 +21,6 @@ test.describe('refresher: basic', () => { }); test.describe('native refresher', () => { - test('should load more items when performing a pull-to-refresh', async ({ page }) => { const refresherContent = page.locator('ion-refresher-content'); refresherContent.evaluateHandle((el: any) => { diff --git a/core/src/components/refresher/test/scroll-target/index.html b/core/src/components/refresher/test/scroll-target/index.html index 1e5ff984162..caa467fa3b4 100644 --- a/core/src/components/refresher/test/scroll-target/index.html +++ b/core/src/components/refresher/test/scroll-target/index.html @@ -1,93 +1,93 @@ + + + Refresher - Custom Scroll Target + + + + + + + - + #inner-scroll { + height: 100%; + overflow-y: auto; + } + + - - - - - Pull To Refresh - - + + + + + Pull To Refresh + + - - - - -
-
- + + + + +
+
+ +
-
- - + + - - + function createItems(start = 0) { + return new Array(30).fill().map((_, i) => start + i + 1); + } + render(); + + diff --git a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts index 14c3673b093..751de7c63f1 100644 --- a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts +++ b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts @@ -4,7 +4,6 @@ import { test } from '@utils/test/playwright'; import { pullToRefresh } from '../test.utils'; test.describe('refresher: custom scroll target', () => { - test.beforeEach(async ({ page }) => { await page.goto('/src/components/refresher/test/scroll-target'); }); @@ -22,7 +21,6 @@ test.describe('refresher: custom scroll target', () => { }); test.describe('native refresher', () => { - test('should load more items when performing a pull-to-refresh', async ({ page }) => { const refresherContent = page.locator('ion-refresher-content'); refresherContent.evaluateHandle((el: any) => { diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 5aba74f72cf..5c61db69f0d 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -1,4 +1,4 @@ -import type { E2EPage } from "@utils/test/playwright"; +import type { E2EPage } from '@utils/test/playwright'; /** * Emulates a pull-to-refresh drag gesture (pulls down and releases). diff --git a/core/src/utils/test/playwright/playwright-declarations.ts b/core/src/utils/test/playwright/playwright-declarations.ts index e3b43554d73..e4837242545 100644 --- a/core/src/utils/test/playwright/playwright-declarations.ts +++ b/core/src/utils/test/playwright/playwright-declarations.ts @@ -28,32 +28,35 @@ export interface E2EPage extends Page { * @param url URL to navigate page to. The url should include scheme, e.g. `https://`. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. */ - goto: (url: string, options?: { - /** - * Referer header value. If provided it will take preference over the referer header value set by - * [page.setExtraHTTPHeaders(headers)](https://playwright.dev/docs/api/class-page#page-set-extra-http-headers). - */ - referer?: string; + goto: ( + url: string, + options?: { + /** + * Referer header value. If provided it will take preference over the referer header value set by + * [page.setExtraHTTPHeaders(headers)](https://playwright.dev/docs/api/class-page#page-set-extra-http-headers). + */ + referer?: string; - /** - * Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be - * changed by using the - * [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-navigation-timeout), - * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout), - * [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout) - * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. - */ - timeout?: number; + /** + * Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be + * changed by using the + * [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-navigation-timeout), + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout), + * [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; - /** - * When to consider operation succeeded, defaults to `load`. Events can be either: - * - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired. - * - `'load'` - consider operation to be finished when the `load` event is fired. - * - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms. - * - `'commit'` - consider operation to be finished when network response is received and the document started loading. - */ - waitUntil?: "load" | "domcontentloaded" | "networkidle" | "commit"; - }) => Promise; + /** + * When to consider operation succeeded, defaults to `load`. Events can be either: + * - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired. + * - `'load'` - consider operation to be finished when the `load` event is fired. + * - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms. + * - `'commit'` - consider operation to be finished when network response is received and the document started loading. + */ + waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; + } + ) => Promise; /** * Find an element by selector. * See https://playwright.dev/docs/locators for more information. diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index 24199a5694f..f27f078f4c9 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -68,5 +68,5 @@ export const test = base.extend({ page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { page = await extendPageFixture(page, testInfo); await use(page); - } + }, }); From c276ea7b2c9efe50f435fb2e4d1c0d46365b0aaf Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 23 May 2022 16:19:56 -0400 Subject: [PATCH 05/11] chore(): wait for changes to avoid flaky tests --- core/src/components/refresher/test/test.utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 5c61db69f0d..105aae65679 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -13,6 +13,8 @@ import type { E2EPage } from '@utils/test/playwright'; const pullToRefresh = async (page: E2EPage, selector = 'body') => { const target = page.locator(selector); + await page.waitForChanges(); + const ev = await page.spyOnEvent('ionRefreshComplete'); const boundingBox = await target.boundingBox(); From d49bb6a7e95b2736961d5069ddf8c5eb850644fb Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 12:13:53 -0400 Subject: [PATCH 06/11] fix(): remove test info checks --- core/src/utils/test/playwright/playwright-page.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index f27f078f4c9..2af4fbfab13 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -37,23 +37,17 @@ type CustomFixtures = { * @param testInfo The test info. * @returns The modified playwright page with extended functionality. */ -export async function extendPageFixture(page: E2EPage, testInfo?: TestInfo) { +export async function extendPageFixture(page: E2EPage, testInfo: TestInfo) { const originalGoto = page.goto.bind(page); const originalLocator = page.locator.bind(page); // Overridden Playwright methods - - if (testInfo) { - page.goto = (url: string, options) => goToPage(page, url, options, testInfo, originalGoto); - } + page.goto = (url: string, options) => goToPage(page, url, options, testInfo, originalGoto); page.setContent = (html: string) => setContent(page, html); page.locator = (selector: string, options?: LocatorOptions) => locator(page, originalLocator, selector, options); // Custom Ionic methods - - if (testInfo) { - page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); - } + page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); page.setIonViewport = () => setIonViewport(page); page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs); page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName); From dd24b4670d81f494ab78cf5e2d4d14f450983b57 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 12:14:08 -0400 Subject: [PATCH 07/11] chore(): reduce mouse move threshold (verifying CI) --- core/src/components/refresher/test/test.utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 105aae65679..576d3e269ec 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -28,7 +28,7 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => { await page.mouse.move(startX, startY); await page.mouse.down(); - for (let i = 0; i < 400; i += 50) { + for (let i = 0; i < 400; i += 20) { await page.mouse.move(startX, startY + i); } From 98a2e573fddd8fefaa47896d4e44461bd80b5ec7 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 12:16:28 -0400 Subject: [PATCH 08/11] chore(): document refresher scroll element --- core/src/components/refresher/refresher.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index a8157183592..56b85f74705 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -449,6 +449,12 @@ export class Refresher implements ComponentInterface { const customScrollTarget = contentEl.querySelector(ION_CONTENT_CLASS_SELECTOR); + /** + * Query the custom scroll target (if available), first. In refresher implementations, + * the ion-refresher element will always be a direct child of ion-content (slot="fixed"). By + * querying the custom scroll target first and falling back to the ion-content element, + * the correct scroll element will be returned by the implementation. + */ this.scrollEl = await getScrollElement(customScrollTarget ?? contentEl); /** From 5308be2c9dba6c1b31eaf89380b1a7d3b6054473 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 13:46:57 -0400 Subject: [PATCH 09/11] chore(): wait for refresher state Testing in CI --- core/src/components/refresher/refresher.tsx | 2 ++ core/src/components/refresher/test/test.utils.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index 56b85f74705..4245a801335 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -749,6 +749,7 @@ export class Refresher implements ComponentInterface { // Used internally for styling [`refresher-${mode}`]: true, 'refresher-native': this.nativeRefresher, + 'refresher-inactive': this.state === RefresherState.Inactive, 'refresher-active': this.state !== RefresherState.Inactive, 'refresher-pulling': this.state === RefresherState.Pulling, 'refresher-ready': this.state === RefresherState.Ready, @@ -768,6 +769,7 @@ const enum RefresherState { Refreshing = 1 << 3, Cancelling = 1 << 4, Completing = 1 << 5, + Stable = 1 << 6, _BUSY_ = Refreshing | Cancelling | Completing, } diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 576d3e269ec..bc2ed4123bb 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -13,7 +13,7 @@ import type { E2EPage } from '@utils/test/playwright'; const pullToRefresh = async (page: E2EPage, selector = 'body') => { const target = page.locator(selector); - await page.waitForChanges(); + await page.waitForSelector('ion-refresher.refresher-inactive', { state: 'attached' }); const ev = await page.spyOnEvent('ionRefreshComplete'); const boundingBox = await target.boundingBox(); From d3c8e29026875218f95b6941f56e947f25bd6511 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 14:52:41 -0400 Subject: [PATCH 10/11] chore(): clean-up --- core/src/components/refresher/refresher.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index 4245a801335..c0bd4b07a94 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -769,7 +769,6 @@ const enum RefresherState { Refreshing = 1 << 3, Cancelling = 1 << 4, Completing = 1 << 5, - Stable = 1 << 6, _BUSY_ = Refreshing | Cancelling | Completing, } From 377a59639fd57de0ada032f173f4912d15bba19f Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 24 May 2022 15:36:17 -0400 Subject: [PATCH 11/11] chore(): use hydrated class --- core/src/components/refresher/refresher.tsx | 1 - core/src/components/refresher/test/test.utils.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index c0bd4b07a94..56b85f74705 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -749,7 +749,6 @@ export class Refresher implements ComponentInterface { // Used internally for styling [`refresher-${mode}`]: true, 'refresher-native': this.nativeRefresher, - 'refresher-inactive': this.state === RefresherState.Inactive, 'refresher-active': this.state !== RefresherState.Inactive, 'refresher-pulling': this.state === RefresherState.Pulling, 'refresher-ready': this.state === RefresherState.Ready, diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index bc2ed4123bb..40910ce864c 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -13,7 +13,7 @@ import type { E2EPage } from '@utils/test/playwright'; const pullToRefresh = async (page: E2EPage, selector = 'body') => { const target = page.locator(selector); - await page.waitForSelector('ion-refresher.refresher-inactive', { state: 'attached' }); + await page.waitForSelector('ion-refresher.hydrated', { state: 'attached' }); const ev = await page.spyOnEvent('ionRefreshComplete'); const boundingBox = await target.boundingBox();