Skip to content

Commit bc8bac1

Browse files
authored
fix: supported switching to iframes that are places in a Shadow Root (#7139)
* test: added test Should switch context between a shadow iframe and the main window * fix: fixed function findIframeByWindow * test: fixed tests * refactor: deleted unnecessary condition * test: fixed test * test: added test Should switch context between a nested shadow iframe and the main window * refactor: refactored findIframeByWindow * test: fixed tests
1 parent 10eef98 commit bc8bac1

File tree

5 files changed

+136
-8
lines changed

5 files changed

+136
-8
lines changed

src/client/core/utils/dom.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const getElementStyleProperty = hammerhead.utils.style.get;
1010

1111
export const getActiveElement = hammerhead.utils.dom.getActiveElement;
1212
export const findDocument = hammerhead.utils.dom.findDocument;
13+
export const find = hammerhead.utils.dom.find;
1314
export const isElementInDocument = hammerhead.utils.dom.isElementInDocument;
1415
export const isElementInIframe = hammerhead.utils.dom.isElementInIframe;
1516
export const getIframeByElement = hammerhead.utils.dom.getIframeByElement;
@@ -390,8 +391,16 @@ export function isTopWindow (win) {
390391
}
391392
}
392393

393-
export function findIframeByWindow (iframeWindow, iframeDestinationWindow) {
394-
const iframes = (iframeDestinationWindow || window).document.getElementsByTagName('iframe');
394+
export function findIframeByWindow (iframeWindow) {
395+
const iframes = [];
396+
397+
find(document, '*', elem => {
398+
if (elem.tagName === 'IFRAME')
399+
iframes.push(elem);
400+
401+
if (elem.shadowRoot)
402+
find(elem.shadowRoot, 'iframe', iframe => iframes.push(iframe));
403+
});
395404

396405
for (let i = 0; i < iframes.length; i++) {
397406
if (nativeMethods.contentWindowGetter.call(iframes[i]) === iframeWindow)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Iframe</title>
6+
</head>
7+
<body>
8+
<button id="btn">button</button>
9+
10+
<div id="shadow-element"></div>
11+
12+
<script>
13+
const shadowElement = document.getElementById('shadow-element');
14+
15+
shadowElement.attachShadow({mode: 'open'});
16+
17+
const shadowRoot = shadowElement.shadowRoot;
18+
const shadowIframe = document.createElement('iframe');
19+
20+
shadowIframe.src = 'nested-iframe.html';
21+
shadowIframe.width = '500px';
22+
shadowIframe.height = '300px';
23+
24+
shadowRoot.appendChild(shadowIframe);
25+
26+
document.getElementById('btn').addEventListener('click', function () {
27+
window.top.iframeBtnClickCount = (window.top.iframeBtnClickCount || 0) + 1;
28+
});
29+
</script>
30+
</body>
31+
</html>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Title</title>
6+
</head>
7+
<body>
8+
<button id="btn">button</button>
9+
10+
<div id="shadow-element"></div>
11+
12+
<script>
13+
const shadowElement = document.getElementById('shadow-element');
14+
15+
shadowElement.attachShadow({mode: 'open'});
16+
17+
const shadowRoot = shadowElement.shadowRoot;
18+
const shadowIframe = document.createElement('iframe');
19+
20+
shadowIframe.src = 'shadow-iframe.html';
21+
shadowIframe.width = '500px';
22+
shadowIframe.height = '300px';
23+
24+
shadowRoot.appendChild(shadowIframe);
25+
26+
document.getElementById('btn').addEventListener('click', function () {
27+
window.btnClickCount = (window.btnClickCount || 0) + 1;
28+
});
29+
</script>
30+
</body>
31+
</html>

test/functional/fixtures/api/es-next/iframe-switching/test.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ describe('[API] t.switchToIframe(), t.switchToMainWindow()', function () {
2323
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click on element in a nested iframe', DEFAULT_RUN_OPTIONS);
2424
});
2525

26+
it('Should switch context between a shadow iframe and the main window', function () {
27+
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click on an element in a shadow iframe and return to the main window', {
28+
...DEFAULT_RUN_OPTIONS,
29+
skip: ['ie', 'edge'],
30+
});
31+
});
32+
33+
it('Should switch context between a nested shadow iframe and the main window', function () {
34+
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click on element in a nested shadow iframe', {
35+
...DEFAULT_RUN_OPTIONS,
36+
skip: ['ie', 'edge'],
37+
});
38+
});
39+
2640
it('Should wait while a target iframe is loaded', function () {
2741
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click in a slowly loading iframe', DEFAULT_RUN_OPTIONS);
2842
});
@@ -64,7 +78,7 @@ describe('[API] t.switchToIframe(), t.switchToMainWindow()', function () {
6478
expect(errs[0]).to.contains(
6579
'The specified selector does not match any element in the DOM tree.' +
6680
' > | Selector(\'#non-existent\')');
67-
expect(errs[0]).to.contains("> 56 | await t.switchToIframe('#non-existent');");
81+
expect(/> *\d* *\| *await t\.switchToIframe\('#non-existent'\);/.test(errs[0])).ok;
6882
});
6983
});
7084

@@ -81,7 +95,7 @@ describe('[API] t.switchToIframe(), t.switchToMainWindow()', function () {
8195
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Try to switch to an incorrect element', { shouldFail: true })
8296
.catch(function (errs) {
8397
expect(errs[0]).to.contains('The action element is expected to be an <iframe>.');
84-
expect(errs[0]).to.contains("> 74 | await t.switchToIframe('body');");
98+
expect(/> *\d* *\| *await t\.switchToIframe\('body'\);/.test(errs[0])).ok;
8599
});
86100
});
87101

@@ -92,23 +106,23 @@ describe('[API] t.switchToIframe(), t.switchToMainWindow()', function () {
92106
})
93107
.catch(function (errs) {
94108
expect(errs[0]).to.contains('Content of the iframe to which you are switching did not load.');
95-
expect(errs[0]).to.contains("> 189 | .switchToIframe('#too-slowly-loading-iframe')");
109+
expect(/> *\d* *\| *\.switchToIframe\('#too-slowly-loading-iframe'\)/.test(errs[0])).ok;
96110
});
97111
});
98112

99113
it('Should raise an error when trying to execute an action in an unavailable iframe', function () {
100114
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click in a removed iframe', DEFAULT_FAILED_RUN_OPTIONS)
101115
.catch(function (errs) {
102116
expect(errs[0]).to.contains('The iframe in which the test is currently operating does not exist anymore.');
103-
expect(errs[0]).to.contains("> 93 | .click('#btn');");
117+
expect(/> *\d* *\| *\.click\('#btn'\);/.test(errs[0])).ok;
104118
});
105119
});
106120

107121
it('Should raise an error when trying to execute an action in an invisible iframe', function () {
108122
return runTests('./testcafe-fixtures/iframe-switching-test.js', 'Click in an invisible iframe', DEFAULT_FAILED_RUN_OPTIONS)
109123
.catch(function (errs) {
110124
expect(errs[0]).to.contains('The iframe in which the test is currently operating is not visible anymore.');
111-
expect(errs[0]).to.contains("> 201 | .click('#btn');");
125+
expect(/> *\d* *\| *\.click\('#btn'\);/.test(errs[0])).ok;
112126
});
113127
});
114128

@@ -122,7 +136,7 @@ describe('[API] t.switchToIframe(), t.switchToMainWindow()', function () {
122136
})
123137
.catch(function (errs) {
124138
expect(errs[0]).to.contains('Content of the iframe in which the test is currently operating did not load.');
125-
expect(errs[0]).to.contains("> 209 | .click('#second-page-btn');");
139+
expect(/> *\d* *\| *\.click\('#second-page-btn'\);/.test(errs[0])).ok;
126140
});
127141
});
128142
});

test/functional/fixtures/api/es-next/iframe-switching/testcafe-fixtures/iframe-switching-test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,49 @@ test('Click on element in a nested iframe', async t => {
5252
expect(iframeBtnClickCount).eql(1);
5353
});
5454

55+
test.page`http://localhost:3000/fixtures/api/es-next/iframe-switching/pages/shadow.html`
56+
('Click on an element in a shadow iframe and return to the main window', async t => {
57+
await t
58+
.switchToIframe(() => document.querySelector('#shadow-element').shadowRoot.querySelector('iframe'))
59+
.click('#btn')
60+
.switchToMainWindow()
61+
.click('#btn');
62+
63+
const btnClickCount = await getBtnClickCount();
64+
const iframeBtnClickCount = await getIframeBtnClickCount();
65+
66+
expect(btnClickCount).eql(1);
67+
expect(iframeBtnClickCount).eql(1);
68+
});
69+
70+
test.page`http://localhost:3000/fixtures/api/es-next/iframe-switching/pages/shadow.html`
71+
('Click on element in a nested shadow iframe', async t => {
72+
await t
73+
.switchToIframe(() => document.querySelector('#shadow-element').shadowRoot.querySelector('iframe'))
74+
.switchToIframe(() => document.querySelector('#shadow-element').shadowRoot.querySelector('iframe'))
75+
.click('#btn')
76+
.switchToMainWindow()
77+
.click('#btn');
78+
79+
let btnClickCount = await getBtnClickCount();
80+
const nestedIframeBtnClickCount = await getNestedIframeBtnClickCount();
81+
82+
expect(btnClickCount).eql(1);
83+
expect(nestedIframeBtnClickCount).eql(1);
84+
85+
await t
86+
.switchToIframe(() => document.querySelector('#shadow-element').shadowRoot.querySelector('iframe'))
87+
.click('#btn')
88+
.switchToMainWindow()
89+
.click('#btn');
90+
91+
btnClickCount = await getBtnClickCount();
92+
const iframeBtnClickCount = await getIframeBtnClickCount();
93+
94+
expect(btnClickCount).eql(2);
95+
expect(iframeBtnClickCount).eql(1);
96+
});
97+
5598
test('Switch to a non-existent iframe', async t => {
5699
await t.switchToIframe('#non-existent');
57100
});

0 commit comments

Comments
 (0)