test(popover): migrate tests to Playwright (#25176)
Co-authored-by: Liam DeBeasi <liamdebeasi@users.noreply.github.com>
@ -1,61 +0,0 @@
|
|||||||
import { newE2EPage } from '@stencil/core/testing';
|
|
||||||
|
|
||||||
test('popover - arrow side: top', async () => {
|
|
||||||
await testPopover('top', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: right', async () => {
|
|
||||||
await testPopover('right', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: bottom', async () => {
|
|
||||||
await testPopover('bottom', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: left', async () => {
|
|
||||||
await testPopover('left', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: start', async () => {
|
|
||||||
await testPopover('start', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: end', async () => {
|
|
||||||
await testPopover('end', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: start, rtl', async () => {
|
|
||||||
await testPopover('start', true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover - arrow side: end, rtl', async () => {
|
|
||||||
await testPopover('end', true);
|
|
||||||
});
|
|
||||||
|
|
||||||
const testPopover = async (side: string, isRTL = false) => {
|
|
||||||
const rtl = isRTL ? '&rtl=true' : '';
|
|
||||||
const page = await newE2EPage({ url: `/src/components/popover/test/arrow?ionic:_testing=true${rtl}` });
|
|
||||||
|
|
||||||
const POPOVER_CLASS = `${side}-popover`;
|
|
||||||
const TRIGGER_ID = `${side}-trigger`;
|
|
||||||
const screenshotCompares = [];
|
|
||||||
|
|
||||||
const trigger = await page.find(`#${TRIGGER_ID}`);
|
|
||||||
|
|
||||||
await page.evaluate((POPOVER_TRIGGER_ID) => {
|
|
||||||
const popoverTrigger = document.querySelector(`#${POPOVER_TRIGGER_ID}`);
|
|
||||||
popoverTrigger?.scrollIntoView({ block: 'center' });
|
|
||||||
}, TRIGGER_ID);
|
|
||||||
|
|
||||||
trigger.click();
|
|
||||||
|
|
||||||
await page.waitForSelector(`.${POPOVER_CLASS}`);
|
|
||||||
const popover = await page.find(`.${POPOVER_CLASS}`);
|
|
||||||
await popover.waitForVisible();
|
|
||||||
|
|
||||||
screenshotCompares.push(await page.compareScreenshot());
|
|
||||||
|
|
||||||
for (const screenshotCompare of screenshotCompares) {
|
|
||||||
expect(screenshotCompare).toMatchScreenshot();
|
|
||||||
}
|
|
||||||
};
|
|
@ -13,10 +13,13 @@
|
|||||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
<style>
|
<style>
|
||||||
|
ion-app > ion-content {
|
||||||
|
--background: #dddddd;
|
||||||
|
}
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-row-gap: 20px;
|
grid-row-gap: 80px;
|
||||||
grid-column-gap: 20px;
|
grid-column-gap: 20px;
|
||||||
|
|
||||||
padding: 200px;
|
padding: 200px;
|
||||||
@ -54,42 +57,48 @@
|
|||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>Top</h2>
|
<h2>Top</h2>
|
||||||
<ion-button id="top-trigger">Click to Open</ion-button>
|
<ion-button id="top-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="top-popover" trigger="top-trigger" side="top" size="cover">
|
<ion-popover show-backdrop="false" class="top-popover" trigger="top-trigger" side="top" size="cover">
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>Right</h2>
|
<h2>Right</h2>
|
||||||
<ion-button id="right-trigger">Click to Open</ion-button>
|
<ion-button id="right-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="right-popover" trigger="right-trigger" side="right" size="cover">
|
<ion-popover show-backdrop="false" class="right-popover" trigger="right-trigger" side="right" size="cover">
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>Bottom</h2>
|
<h2>Bottom</h2>
|
||||||
<ion-button id="bottom-trigger">Click to Open</ion-button>
|
<ion-button id="bottom-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="bottom-popover" trigger="bottom-trigger" side="bottom" size="cover">
|
<ion-popover
|
||||||
|
show-backdrop="false"
|
||||||
|
class="bottom-popover"
|
||||||
|
trigger="bottom-trigger"
|
||||||
|
side="bottom"
|
||||||
|
size="cover"
|
||||||
|
>
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>Left</h2>
|
<h2>Left</h2>
|
||||||
<ion-button id="left-trigger">Click to Open</ion-button>
|
<ion-button id="left-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="left-popover" trigger="left-trigger" side="left" size="cover">
|
<ion-popover show-backdrop="false" class="left-popover" trigger="left-trigger" side="left" size="cover">
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>Start</h2>
|
<h2>Start</h2>
|
||||||
<ion-button id="start-trigger">Click to Open</ion-button>
|
<ion-button id="start-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="start-popover" trigger="start-trigger" side="start" size="cover">
|
<ion-popover show-backdrop="false" class="start-popover" trigger="start-trigger" side="start" size="cover">
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
<h2>End</h2>
|
<h2>End</h2>
|
||||||
<ion-button id="end-trigger">Click to Open</ion-button>
|
<ion-button id="end-trigger">Click to Open</ion-button>
|
||||||
<ion-popover class="end-popover" trigger="end-trigger" side="end" size="cover">
|
<ion-popover show-backdrop="false" class="end-popover" trigger="end-trigger" side="end" size="cover">
|
||||||
<ion-content class="ion-padding"> Hello World </ion-content>
|
<ion-content class="ion-padding"> Hello World </ion-content>
|
||||||
</ion-popover>
|
</ion-popover>
|
||||||
</div>
|
</div>
|
||||||
|
23
core/src/components/popover/test/arrow/popover.e2e.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { expect } from '@playwright/test';
|
||||||
|
import { test, Viewports } from '@utils/test/playwright';
|
||||||
|
|
||||||
|
import { openPopover } from '../test.utils';
|
||||||
|
|
||||||
|
test.describe('popover: arrow rendering', async () => {
|
||||||
|
/**
|
||||||
|
* The popovers have showBackdrop=false so we can open all of them at once
|
||||||
|
* and massively cut down on screenshots taken. The content has its own
|
||||||
|
* backdrop so you can still see the popovers.
|
||||||
|
*/
|
||||||
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/popover/test/arrow');
|
||||||
|
await page.setViewportSize(Viewports.tablet.portrait); // avoid extra-long viewport screenshots
|
||||||
|
|
||||||
|
const sides = ['top', 'right', 'bottom', 'left', 'start', 'end'];
|
||||||
|
for (const side of sides) {
|
||||||
|
await openPopover(page, `${side}-trigger`, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await page.screenshot()).toMatchSnapshot(`popover-arrow-${page.getSnapshotSettings()}.png`);
|
||||||
|
});
|
||||||
|
});
|
After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 106 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 101 KiB |
@ -20,52 +20,6 @@
|
|||||||
|
|
||||||
<body>
|
<body>
|
||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-content class="ion-padding" id="content">
|
|
||||||
<ion-button
|
|
||||||
id="basic-popover"
|
|
||||||
expand="block"
|
|
||||||
onclick="presentPopover({ component: 'profile-page', event: event, htmlAttributes: { 'data-testid': 'basic-popover' } })"
|
|
||||||
>
|
|
||||||
Show Popover</ion-button
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
id="translucent-popover"
|
|
||||||
expand="block"
|
|
||||||
onclick="presentPopover({ component: 'translucent-page', event: event, translucent: true })"
|
|
||||||
>Show Translucent Popover</ion-button
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
id="long-list-popover"
|
|
||||||
expand="block"
|
|
||||||
color="secondary"
|
|
||||||
onclick="presentPopover({ component: 'list-page', event: event })"
|
|
||||||
>Show Long List Popover</ion-button
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
id="no-event-popover"
|
|
||||||
expand="block"
|
|
||||||
color="danger"
|
|
||||||
onclick="presentPopover({ component: 'profile-page' })"
|
|
||||||
>No Event Popover</ion-button
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
id="custom-class-popover"
|
|
||||||
expand="block"
|
|
||||||
color="tertiary"
|
|
||||||
onclick="presentPopover({ component: 'translucent-page', event: event, cssClass: 'my-custom-class' })"
|
|
||||||
>Custom Class Popover</ion-button
|
|
||||||
>
|
|
||||||
<ion-button id="header-popover" expand="block" onclick="presentPopover({ component: 'header-page' })"
|
|
||||||
>Popover With Header</ion-button
|
|
||||||
>
|
|
||||||
<ion-button
|
|
||||||
id="translucent-header-popover"
|
|
||||||
expand="block"
|
|
||||||
onclick="presentPopover({ component: 'translucent-header-page' })"
|
|
||||||
>Popover With Translucent Header</ion-button
|
|
||||||
>
|
|
||||||
</ion-content>
|
|
||||||
|
|
||||||
<ion-content class="ion-padding" id="content">
|
<ion-content class="ion-padding" id="content">
|
||||||
<ion-button
|
<ion-button
|
||||||
id="basic-popover"
|
id="basic-popover"
|
||||||
|
@ -1,142 +1,65 @@
|
|||||||
import type { E2EPage } from '@stencil/core/testing';
|
import { expect } from '@playwright/test';
|
||||||
import { newE2EPage } from '@stencil/core/testing';
|
import type { E2EPage } from '@utils/test/playwright';
|
||||||
|
import { test } from '@utils/test/playwright';
|
||||||
|
|
||||||
import { testPopover } from '../test.utils';
|
import { openPopover, screenshotPopover } from '../test.utils';
|
||||||
|
|
||||||
const DIRECTORY = 'basic';
|
test.describe('popover: rendering', async () => {
|
||||||
|
test('should not have visual regressions', async ({ page }) => {
|
||||||
|
const buttonIDs = [
|
||||||
|
'basic-popover',
|
||||||
|
'translucent-popover',
|
||||||
|
'long-list-popover',
|
||||||
|
'no-event-popover',
|
||||||
|
'custom-class-popover',
|
||||||
|
'header-popover',
|
||||||
|
'translucent-header-popover',
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
for (const id of buttonIDs) {
|
||||||
* Focusing happens async inside of popover so we need
|
await screenshotPopover(page, id, 'basic');
|
||||||
* to wait for the requestAnimationFrame to fire.
|
}
|
||||||
*/
|
|
||||||
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
|
|
||||||
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActiveElementSelectionStart = (page: E2EPage) => {
|
|
||||||
return page.evaluate(() =>
|
|
||||||
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getActiveElementScrollTop = (page: E2EPage) => {
|
|
||||||
return page.evaluate(() => {
|
|
||||||
// Returns the closest ion-textarea or active element
|
|
||||||
const target = document.activeElement!.closest('ion-textarea') ?? document.activeElement;
|
|
||||||
return target!.scrollTop;
|
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
test('popover: basic', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#basic-popover');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('popover: translucent', async () => {
|
test.describe('popover: htmlAttributes', async () => {
|
||||||
await testPopover(DIRECTORY, '#translucent-popover');
|
test('should inherit attributes on host', async ({ page }) => {
|
||||||
|
await page.goto('/src/components/popover/test/basic');
|
||||||
|
await openPopover(page, 'basic-popover');
|
||||||
|
|
||||||
|
const alert = page.locator('ion-popover');
|
||||||
|
expect(alert).toHaveAttribute('data-testid', 'basic-popover');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('popover: long list', async () => {
|
test.describe('popover: focus trap', async () => {
|
||||||
await testPopover(DIRECTORY, '#long-list-popover');
|
test.beforeEach(async ({ page }) => {
|
||||||
});
|
await page.goto('/src/components/popover/test/basic');
|
||||||
|
});
|
||||||
|
|
||||||
test('popover: no event', async () => {
|
test('should focus the first ion-item on ArrowDown', async ({ page }) => {
|
||||||
await testPopover(DIRECTORY, '#no-event-popover');
|
await openPopover(page, 'basic-popover');
|
||||||
});
|
|
||||||
|
|
||||||
test('popover: custom class', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#custom-class-popover');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover: header', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#header-popover');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover: translucent header', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#translucent-header-popover');
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RTL Tests
|
|
||||||
*/
|
|
||||||
|
|
||||||
test('popover:rtl: basic', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#basic-popover', true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: translucent', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#translucent-popover', true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: long list', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#long-list-popover', true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: no event', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#no-event-popover', true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: custom class', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#custom-class-popover', true, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: header', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#header-popover', true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover:rtl: translucent header', async () => {
|
|
||||||
await testPopover(DIRECTORY, '#translucent-header-popover', true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('popover: htmlAttributes', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
|
||||||
|
|
||||||
await page.click('#basic-popover');
|
|
||||||
await page.waitForSelector('#basic-popover');
|
|
||||||
|
|
||||||
const alert = await page.find('ion-popover');
|
|
||||||
|
|
||||||
expect(alert).not.toBe(null);
|
|
||||||
await alert.waitForVisible();
|
|
||||||
|
|
||||||
const attribute = await page.evaluate(() => document.querySelector('ion-popover')!.getAttribute('data-testid'));
|
|
||||||
|
|
||||||
expect(attribute).toEqual('basic-popover');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('popover: focus trap', () => {
|
|
||||||
it('should focus the first ion-item on ArrowDown', async () => {
|
|
||||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
|
||||||
|
|
||||||
await page.click('#basic-popover');
|
|
||||||
|
|
||||||
const popover = await page.find('ion-popover');
|
|
||||||
|
|
||||||
expect(popover).not.toBe(null);
|
|
||||||
await popover.waitForVisible();
|
|
||||||
|
|
||||||
await page.keyboard.press('ArrowDown');
|
await page.keyboard.press('ArrowDown');
|
||||||
|
|
||||||
await expectActiveElementTextToEqual(page, 'Item 0');
|
await expectActiveElementTextToEqual(page, 'Item 0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with ion-item children', async () => {
|
test('should trap focus', async ({ page, browserName }) => {
|
||||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
await openPopover(page, 'basic-popover');
|
||||||
|
|
||||||
await page.click('#basic-popover');
|
|
||||||
await page.waitForSelector('#basic-popover');
|
|
||||||
|
|
||||||
const popover = await page.find('ion-popover');
|
|
||||||
|
|
||||||
expect(popover).not.toBe(null);
|
|
||||||
await popover.waitForVisible();
|
|
||||||
|
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
|
|
||||||
await expectActiveElementTextToEqual(page, 'Item 0');
|
await expectActiveElementTextToEqual(page, 'Item 0');
|
||||||
|
|
||||||
await page.keyboard.down('Shift');
|
await page.keyboard.down('Shift');
|
||||||
|
if (browserName === 'webkit') {
|
||||||
|
await page.keyboard.down('Alt');
|
||||||
|
}
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
await page.keyboard.up('Shift');
|
await page.keyboard.up('Shift');
|
||||||
|
if (browserName === 'webkit') {
|
||||||
|
await page.keyboard.up('Alt');
|
||||||
|
}
|
||||||
|
|
||||||
await expectActiveElementTextToEqual(page, 'Item 3');
|
await expectActiveElementTextToEqual(page, 'Item 3');
|
||||||
|
|
||||||
@ -161,19 +84,17 @@ describe('popover: focus trap', () => {
|
|||||||
await expectActiveElementTextToEqual(page, 'Item 3');
|
await expectActiveElementTextToEqual(page, 'Item 3');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not override keyboard interactions for textarea elements', async () => {
|
test('should not override keyboard interactions for textarea elements', async ({ page, browserName }) => {
|
||||||
const page = await newE2EPage({ url: '/src/components/popover/test/basic?ionic:_testing=true' });
|
await openPopover(page, 'popover-with-textarea');
|
||||||
|
await page.waitForFunction(() => document.activeElement?.tagName === 'ION-POPOVER');
|
||||||
await page.waitForSelector('#popover-with-textarea');
|
|
||||||
await page.click('#popover-with-textarea');
|
|
||||||
|
|
||||||
const popover = await page.find('ion-popover');
|
|
||||||
await popover.waitForVisible();
|
|
||||||
|
|
||||||
await page.waitForFunction('document.activeElement.tagName === "ION-POPOVER"');
|
|
||||||
|
|
||||||
await page.keyboard.press('Tab');
|
await page.keyboard.press('Tab');
|
||||||
// Checking within ion-textarea
|
|
||||||
|
// for Firefox, ion-textarea is focused first
|
||||||
|
// need to tab again to get to native input
|
||||||
|
if (browserName === 'firefox') {
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
}
|
||||||
|
|
||||||
let activeElementTagName = await page.evaluate(() => document.activeElement!.tagName);
|
let activeElementTagName = await page.evaluate(() => document.activeElement!.tagName);
|
||||||
let scrollTop = null;
|
let scrollTop = null;
|
||||||
@ -235,3 +156,27 @@ describe('popover: focus trap', () => {
|
|||||||
expect(scrollTop).toBeGreaterThanOrEqual(previousScrollTop);
|
expect(scrollTop).toBeGreaterThanOrEqual(previousScrollTop);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO(FW-1424): convert these to Playwright assertions where possible
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Focusing happens async inside of popover so we need
|
||||||
|
* to wait for the requestAnimationFrame to fire.
|
||||||
|
*/
|
||||||
|
const expectActiveElementTextToEqual = async (page: E2EPage, textValue: string) => {
|
||||||
|
await page.evaluate((text) => document.activeElement!.textContent === text, textValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActiveElementSelectionStart = (page: E2EPage) => {
|
||||||
|
return page.evaluate(() =>
|
||||||
|
document.activeElement instanceof HTMLTextAreaElement ? document.activeElement.selectionStart : null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActiveElementScrollTop = (page: E2EPage) => {
|
||||||
|
return page.evaluate(() => {
|
||||||
|
// Returns the closest ion-textarea or active element
|
||||||
|
const target = document.activeElement!.closest('ion-textarea') ?? document.activeElement;
|
||||||
|
return target!.scrollTop;
|
||||||
|
});
|
||||||
|
};
|
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 74 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 60 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 95 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 162 KiB |
After Width: | Height: | Size: 281 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 279 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 87 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 163 KiB |
After Width: | Height: | Size: 281 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 279 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 34 KiB |