mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-19 03:32:21 +08:00
fix(refresher): attach scroll listener to custom scroll target (#25335)
Resolves #25318
This commit is contained in:
@ -4,7 +4,12 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, readTas
|
|||||||
import { getIonMode } from '../../global/ionic-global';
|
import { getIonMode } from '../../global/ionic-global';
|
||||||
import type { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
import type { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||||
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
|
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 { clamp, getElementRoot, raf, transitionEndAsync } from '../../utils/helpers';
|
||||||
import { hapticImpact } from '../../utils/native/haptic';
|
import { hapticImpact } from '../../utils/native/haptic';
|
||||||
|
|
||||||
@ -436,13 +441,21 @@ export class Refresher implements ComponentInterface {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentEl = findClosestIonContent(this.el);
|
const contentEl = this.el.closest(ION_CONTENT_ELEMENT_SELECTOR);
|
||||||
if (!contentEl) {
|
if (!contentEl) {
|
||||||
printIonContentErrorMsg(this.el);
|
printIonContentErrorMsg(this.el);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scrollEl = await getScrollElement(contentEl);
|
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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the host `ion-content` directly (if it is available), to use its
|
* Query the host `ion-content` directly (if it is available), to use its
|
||||||
@ -452,9 +465,7 @@ export class Refresher implements ComponentInterface {
|
|||||||
* This makes it so that implementers do not need to re-create the background content
|
* This makes it so that implementers do not need to re-create the background content
|
||||||
* element and styles.
|
* element and styles.
|
||||||
*/
|
*/
|
||||||
const backgroundContentHost = this.el.closest('ion-content') ?? contentEl;
|
this.backgroundContentEl = getElementRoot(contentEl ?? customScrollTarget).querySelector(
|
||||||
|
|
||||||
this.backgroundContentEl = getElementRoot(backgroundContentHost).querySelector(
|
|
||||||
'#background-content'
|
'#background-content'
|
||||||
) as HTMLElement;
|
) as HTMLElement;
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -53,7 +53,7 @@
|
|||||||
refresher.complete();
|
refresher.complete();
|
||||||
render();
|
render();
|
||||||
// Custom event consumed by e2e tests
|
// Custom event consumed by e2e tests
|
||||||
document.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
window.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
||||||
});
|
});
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
|
41
core/src/components/refresher/test/basic/refresher.e2e.ts
Normal file
41
core/src/components/refresher/test/basic/refresher.e2e.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
expect(await items.count()).toBe(60);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -44,8 +44,8 @@
|
|||||||
<ion-refresher id="refresher" slot="fixed">
|
<ion-refresher id="refresher" slot="fixed">
|
||||||
<ion-refresher-content></ion-refresher-content>
|
<ion-refresher-content></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<div id="content" class="ion-content-scroll-host">
|
<div id="content">
|
||||||
<div id="inner-scroll">
|
<div id="inner-scroll" class="ion-content-scroll-host">
|
||||||
<ion-list id="list"></ion-list>
|
<ion-list id="list"></ion-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,7 +65,7 @@
|
|||||||
refresher.complete();
|
refresher.complete();
|
||||||
render();
|
render();
|
||||||
// Custom event consumed by e2e tests
|
// Custom event consumed by e2e tests
|
||||||
document.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
window.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
||||||
});
|
});
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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();
|
|
||||||
});
|
|
@ -1,131 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" dir="ltr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>Refresher - Spec</title>
|
|
||||||
<meta
|
|
||||||
name="viewport"
|
|
||||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
|
||||||
/>
|
|
||||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
|
|
||||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
|
|
||||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
|
||||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
|
||||||
</head>
|
|
||||||
<style>
|
|
||||||
ion-spinner {
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<body>
|
|
||||||
<ion-app>
|
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button default-href="/" text="Mailboxes"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>All Inboxes</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button>Edit</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-content>
|
|
||||||
<ion-refresher id="refresher" slot="fixed">
|
|
||||||
<ion-refresher-content></ion-refresher-content>
|
|
||||||
</ion-refresher>
|
|
||||||
|
|
||||||
<ion-header collapse="condense">
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-title size="large">All Inboxes</ion-title>
|
|
||||||
</ion-toolbar>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-searchbar></ion-searchbar>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
|
|
||||||
<ion-list id="list"></ion-list>
|
|
||||||
|
|
||||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
|
||||||
<ion-fab-button onclick="toggleHeader()">
|
|
||||||
<ion-icon name="settings"></ion-icon>
|
|
||||||
</ion-fab-button>
|
|
||||||
</ion-fab>
|
|
||||||
</ion-content>
|
|
||||||
</ion-app>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const toggleHeader = () => {
|
|
||||||
const app = document.querySelector('ion-app');
|
|
||||||
const header = app.querySelector('ion-header');
|
|
||||||
|
|
||||||
if (header) {
|
|
||||||
header.parentNode.removeChild(header);
|
|
||||||
} else {
|
|
||||||
app.insertAdjacentHTML(
|
|
||||||
'afterbegin',
|
|
||||||
`
|
|
||||||
<ion-header>
|
|
||||||
<ion-toolbar>
|
|
||||||
<ion-buttons slot="start">
|
|
||||||
<ion-back-button default-href="/" text="Mailboxes"></ion-back-button>
|
|
||||||
</ion-buttons>
|
|
||||||
<ion-title>All Inboxes</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button>Edit</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = [];
|
|
||||||
for (var i = 0; i < 30; i++) {
|
|
||||||
items.push(i + 1);
|
|
||||||
}
|
|
||||||
const list = document.getElementById('list');
|
|
||||||
const refresher = document.getElementById('refresher');
|
|
||||||
|
|
||||||
refresher.addEventListener('ionPull', () => {
|
|
||||||
console.log('ionPull');
|
|
||||||
});
|
|
||||||
|
|
||||||
refresher.addEventListener('ionStart', () => {
|
|
||||||
console.log('ionStart');
|
|
||||||
});
|
|
||||||
|
|
||||||
refresher.addEventListener('ionRefresh', async function () {
|
|
||||||
console.log('Loading data...');
|
|
||||||
const data = await getAsyncData();
|
|
||||||
items = items.concat(data);
|
|
||||||
refresher.complete();
|
|
||||||
render();
|
|
||||||
console.log('Done');
|
|
||||||
});
|
|
||||||
function render() {
|
|
||||||
let html = '';
|
|
||||||
for (let item of items) {
|
|
||||||
html += `<ion-item button>${item}</ion-item>`;
|
|
||||||
}
|
|
||||||
list.innerHTML = html;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}, 5000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
render();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,5 +1,4 @@
|
|||||||
import type { E2EPage } from '@stencil/core/testing';
|
import type { E2EPage } from '@utils/test/playwright';
|
||||||
import { dragElementBy } from '@utils/test';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emulates a pull-to-refresh drag gesture (pulls down and releases).
|
* Emulates a pull-to-refresh drag gesture (pulls down and releases).
|
||||||
@ -12,10 +11,28 @@ import { dragElementBy } from '@utils/test';
|
|||||||
* @param selector The element selector to center the drag gesture on. Defaults to `body`.
|
* @param selector The element selector to center the drag gesture on. Defaults to `body`.
|
||||||
*/
|
*/
|
||||||
const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
||||||
const target = (await page.$(selector))!;
|
const target = page.locator(selector);
|
||||||
|
|
||||||
await dragElementBy(target, page, 0, 200);
|
await page.waitForSelector('ion-refresher.hydrated', { state: 'attached' });
|
||||||
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 += 20) {
|
||||||
|
await page.mouse.move(startX, startY + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.mouse.up();
|
||||||
await ev.next();
|
await ev.next();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import { componentOnReady } from '../helpers';
|
|||||||
import { printRequiredElementError } from '../logging';
|
import { printRequiredElementError } from '../logging';
|
||||||
|
|
||||||
const ION_CONTENT_TAG_NAME = 'ION-CONTENT';
|
const ION_CONTENT_TAG_NAME = 'ION-CONTENT';
|
||||||
const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content';
|
export const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content';
|
||||||
const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host';
|
export const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host';
|
||||||
/**
|
/**
|
||||||
* Selector used for implementations reliant on `<ion-content>` for scroll event changes.
|
* Selector used for implementations reliant on `<ion-content>` for scroll event changes.
|
||||||
*
|
*
|
||||||
|
@ -7,7 +7,7 @@ import type { Page, TestInfo } from '@playwright/test';
|
|||||||
* automatically waits for the Stencil components
|
* automatically waits for the Stencil components
|
||||||
* to be hydrated before proceeding with the test.
|
* 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 { mode, rtl, _testing } = testInfo.project.metadata;
|
||||||
|
|
||||||
const splitUrl = url.split('?');
|
const splitUrl = url.split('?');
|
||||||
@ -38,7 +38,7 @@ export const goto = async (page: Page, url: string, testInfo: TestInfo, original
|
|||||||
|
|
||||||
const result = await Promise.all([
|
const result = await Promise.all([
|
||||||
page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }),
|
page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }),
|
||||||
originalFn(formattedUrl),
|
originalFn(formattedUrl, options),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return result[1];
|
return result[1];
|
||||||
|
@ -28,8 +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
|
* @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.
|
* [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor.
|
||||||
*/
|
*/
|
||||||
goto: (url: string) => Promise<null | Response>;
|
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<null | Response>;
|
||||||
/**
|
/**
|
||||||
* Find an element by selector.
|
* Find an element by selector.
|
||||||
* See https://playwright.dev/docs/locators for more information.
|
* See https://playwright.dev/docs/locators for more information.
|
||||||
@ -58,9 +85,11 @@ export interface E2EPage extends Page {
|
|||||||
* never fires.
|
* never fires.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
|
* ```ts
|
||||||
* const ionChange = await page.spyOnEvent('ionChange');
|
* const ionChange = await page.spyOnEvent('ionChange');
|
||||||
* ...
|
* ...
|
||||||
* await ionChange.next();
|
* await ionChange.next();
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
spyOnEvent: (eventName: string) => Promise<EventSpy>;
|
spyOnEvent: (eventName: string) => Promise<EventSpy>;
|
||||||
_e2eEventsIds: number;
|
_e2eEventsIds: number;
|
||||||
|
@ -31,24 +31,36 @@ type CustomFixtures = {
|
|||||||
page: E2EPage;
|
page: E2EPage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
const originalLocator = page.locator.bind(page);
|
||||||
|
|
||||||
|
// Overridden Playwright methods
|
||||||
|
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
|
||||||
|
page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo);
|
||||||
|
page.setIonViewport = () => setIonViewport(page);
|
||||||
|
page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs);
|
||||||
|
page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName);
|
||||||
|
|
||||||
|
// Custom event behavior
|
||||||
|
await initPageEvents(page);
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
export const test = base.extend<CustomFixtures>({
|
export const test = base.extend<CustomFixtures>({
|
||||||
page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise<void>, testInfo: TestInfo) => {
|
page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise<void>, testInfo: TestInfo) => {
|
||||||
const originalGoto = page.goto.bind(page);
|
page = await extendPageFixture(page, testInfo);
|
||||||
const originalLocator = page.locator.bind(page);
|
|
||||||
|
|
||||||
// Overridden Playwright methods
|
|
||||||
page.goto = (url: string) => goToPage(page, url, testInfo, originalGoto);
|
|
||||||
page.setContent = (html: string) => setContent(page, html);
|
|
||||||
page.locator = (selector: string, options?: LocatorOptions) => locator(page, originalLocator, selector, options);
|
|
||||||
// Custom Ionic methods
|
|
||||||
page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo);
|
|
||||||
page.setIonViewport = () => setIonViewport(page);
|
|
||||||
page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs);
|
|
||||||
page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName);
|
|
||||||
|
|
||||||
// Custom event behavior
|
|
||||||
await initPageEvents(page);
|
|
||||||
|
|
||||||
await use(page);
|
await use(page);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user