Skip to content

core(tap-targets): don't exclude visible position absolute elements from audit #7778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
May 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
728c003
Filter pos absolute elements using elementFromPoint
mattzeunert Mar 28, 2019
90404ff
Restore formatting
mattzeunert Mar 28, 2019
d3a9a8d
Revert "Restore formatting"
mattzeunert Mar 28, 2019
06a624a
Restore formatting
mattzeunert Mar 28, 2019
659fa88
scroll window instead of document
mattzeunert Apr 2, 2019
0f8e2df
Make disable point-events !imporant
mattzeunert Apr 3, 2019
233ee73
Position absolute wrapper for smoke test failure
mattzeunert Apr 3, 2019
d9d40bf
Fix lint
mattzeunert Apr 3, 2019
76cc42f
Fix code cov tests
mattzeunert Apr 3, 2019
c2767c3
Fix code cov
mattzeunert Apr 3, 2019
993dd77
Go further up down when checking if within scrollable area
mattzeunert Apr 3, 2019
a6d9932
Always use fn.toString instead of fnString for rect helpers
mattzeunert Apr 3, 2019
463622c
Code style tweaks
mattzeunert Apr 3, 2019
7f38635
Scroll to target position instead of using while loop
mattzeunert Apr 3, 2019
4b9f808
while loop -> for of loop
mattzeunert Apr 3, 2019
31d8251
Fix exception
mattzeunert Apr 3, 2019
8622bf4
Don't make unreasonable scrollY check
mattzeunert Apr 3, 2019
cb4835f
Comment
mattzeunert Apr 7, 2019
7c1ed2d
Artifact assertions
mattzeunert Apr 7, 2019
1c16558
Revert "Artifact assertions"
mattzeunert Apr 7, 2019
97fe391
Wrap expectations in LHR
mattzeunert Apr 7, 2019
88d5a83
Update smoke test logic to support lhr/audit and artifact assertions
mattzeunert Apr 7, 2019
b499b52
Move artifacts to .tmp
mattzeunert Apr 8, 2019
e91d5cd
Make errorCode top level
mattzeunert Apr 8, 2019
810b37e
Remove empty line
mattzeunert Apr 8, 2019
ecc7091
Fix types and actually assert stuff
mattzeunert Apr 8, 2019
bc81d78
Style tweaks
mattzeunert Apr 8, 2019
fc85de0
Merge comparisons/assertions
mattzeunert Apr 8, 2019
ab754df
Add smoke test for overflow hidden client rects
mattzeunert Apr 9, 2019
8a41fef
Merge branch 'master' into tap-target-position-absolute
mattzeunert Apr 9, 2019
cc44890
Merge branch 'artifacts-assertions' into tap-target-position-absolute
mattzeunert Apr 9, 2019
3593edf
Merge branch 'master' into tap-target-position-absolute
mattzeunert Apr 16, 2019
abaf722
typo
mattzeunert Apr 16, 2019
9dece7f
Check each client rect, remove overflow container check
mattzeunert Apr 17, 2019
32a41ac
Remove save gatherer to file
mattzeunert Apr 17, 2019
4d626a4
More detailed smoke test assertions
mattzeunert Apr 17, 2019
ccf9557
Add failing test case for pos absolute child causing overlap
mattzeunert Apr 17, 2019
d69910a
Remove extra type assertion
mattzeunert Apr 17, 2019
af0fdde
Remove filterClientRectsWithinAncestorsVisibleScrollArea
mattzeunert Apr 17, 2019
6e2f54e
Move disableFixedAndStickyElementPointerEvents call
mattzeunert Apr 17, 2019
7183ac6
Fix position sticky test case
mattzeunert Apr 17, 2019
9353561
Merge branch 'master' into tap-target-position-absolute
mattzeunert Apr 17, 2019
6d02ea5
Text tweak
mattzeunert Apr 17, 2019
f4a9ee4
Use textContent instead of innerHTML
brendankenny Apr 21, 2019
8fd5e2d
No need for Array.from
brendankenny Apr 21, 2019
b5a86d6
Spelling
brendankenny Apr 21, 2019
f20fc20
Make tap targets in SEO smoke test more explicit
mattzeunert Apr 21, 2019
befe66e
Merge branch 'tap-target-position-absolute' of https://github.com/Goo…
mattzeunert Apr 21, 2019
fda7c9e
doc
mattzeunert Apr 21, 2019
aafa240
Fix smoke coverage
mattzeunert Apr 25, 2019
5e3c2f8
Merge branch 'master' into tap-target-position-absolute
brendankenny May 25, 2019
6689f0d
more
brendankenny May 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 77 additions & 24 deletions lighthouse-cli/test/fixtures/seo/seo-tap-targets.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,19 @@
<!-- Big tap target, but it's invisible because it's behind the main content div -->
<a style="background: red; position: absolute; top: 0; bottom: 0; left: 0; right: 0; z-index: -1;"></a>

<a style="background: red; position: sticky; top: 0; left: 0; display: block; width: 100vw; height: 40px; z-index: 1;">sticky</a>
<a style="background: red; position: sticky; top: 0; left: 0; display: block; width: 100vw; height: 30px; z-index: 1;">sticky</a>

<div style="background: white; padding: 20px;">

<div style="height: 300px">
<a
data-gathered-target="large-link-at-bottom-of-page"
style="background: #afa; position: absolute; top: 0; display: block; width: calc(100% - 40px); height: 300px;">
This link is intentionally placed at the top of the page, so that the sticky header is hard to tap on.
It should not fail though because overlap with a sticky element depends on the scroll position.
</a>
</div>

<h1>SEO Tap targets</h1>

<!-- Invisible nodes don't cause failures -->
Expand All @@ -47,14 +57,14 @@ <h1>SEO Tap targets</h1>
width 0 and overflow x hidden
</a>
<!-- Visible target should not fail because nothing overlaps it -->
<a>visible target</a>
<a data-gathered-target="visible-target">visible target</a>
</div>
<br/><br/>


<div style="overflow: hidden; position: relative">
<!-- Should be counted as visible although part of it is outside the scroll container -->
<a>
<a data-gathered-target="target-with-client-rect-outside-scroll-container">
<div style="position: absolute;top: -100px">invisible</div>
visible
</a>
Expand All @@ -64,18 +74,18 @@ <h1>SEO Tap targets</h1>

<!-- Link contains large inline block element - no failure because finger
should be placed in the center of the whole link area -->
<a style="background: red;">
<a style="background: red;" data-gathered-target="link-containing-large-inline-block-element">
<div style="display: inline-block; height: 30px; width: 30px; background: orange;"></div>
Link
</a>
<br/>
<a style="display: block; padding: 30px; background: #ddf;">
<a style="display: block; padding: 30px; background: #ddf;" data-gathered-target="link-next-to-link-containing-large-inline-block-element">
Link that the top one would overlap with, if it weren't for the inline-block child.
</a>

<br/><br/>

<div role="button">
<div role="button" data-gathered-target="tap-target-containing-other-tap-targets">
Tap target with children that are also tap targets should not fail.
(Children should not be counted as independent tap targets that appear
in the list.)
Expand All @@ -85,29 +95,78 @@ <h1>SEO Tap targets</h1>

<br/><br/>

<div style="position: relative">
<a style="display: block; position: absolute; top:0; height: 40px; width: 40px;">inner</a>
<a style="display: block; height: 100px; width: 100px; background: yellowgreen">outer</a>
<!-- The left tap target has a large client rect that would normally overlap with the right tap target,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍 LOVIN' these tests :D

but the left link is overflow hidden, so there should be no failure. We should however
detect both tap targets (and not say the left one is invisible because the center of the big client rect is invisible) -->
<div style="overflow: hidden">
<a
data-gathered-target="child-client-rect-hidden-by-overflow-hidden"
style="display: block; height: 40px; width: 40px; background: #fca; float: left; overflow: hidden; position: relative;">
<div style="background: #afa; height: 40px; width: 80px; position: absolute; left: 30px">
left
</div>
</a>
<a
data-gathered-target="tap-target-next-to-child-client-rect-hidden-by-overflow-hidden"
style="display: block; height: 40px; width: 60px; background: yellowgreen; float: left">right</a>
</div>

<br/><br/>

<!-- Only target that's being overlapped should fail, the overlapping one shouldn't -->
<div>
<a style="display: block; width: 100px; height: 30px;background: #ddd;">
too small target
</a>
<a style="display: block; width: 100px; height: 100px;background: #aaa;">
big enough target
<!--Similar to previous test, except that the position absolute child isn't hidden by "overflow: hidden" on the parent. -->
<div style="overflow: hidden"></div>
<a
data-gathered-target="child-client-rect-overlapping-other-target"
style="display: block; height: 40px; width: 40px; background: #fca; float: left; position: relative;">
<div style="background: #afa; height: 40px; width: 30px; position: absolute; left: 30px">
left
</div>
</a>
<a
data-gathered-target="tap-target-overlapped-by-other-targets-position-absolute-child-rect"
style="display: block; height: 40px; width: 60px; background: yellowgreen; float: left">right</a>
</div>

<br/><br/>

<div style="position: relative">
<a
data-gathered-target="position-absolute-tap-target-fully-contained-in-other-target"
style="display: block; position: absolute; top:0; height: 40px; width: 40px;">inner</a>
<a
data-gathered-target="tap-target-fully-containing-position-absolute-target"
style="display: block; height: 100px; width: 100px; background: yellowgreen">outer</a>
</div>

<br/><br/>

<!-- position: absolute wrapper shouldn't stop not prevent the check (fixed/sticky would) -->
<div style="position: relative; height: 200px;">
<div style="position: absolute;">
<!-- Only target that's being overlapped should fail, the overlapping one shouldn't -->
<a
data-gathered-target="too-small-failing-tap-target"
style="display: block; width: 100px; height: 30px;background: #ddd;">
too small target
</a>
<a
data-gathered-target="large-enough-tap-target-next-to-too-small-tap-target"
style="display: block; width: 100px; height: 100px;background: #aaa;">
big enough target
</a>
</div>
</div>

<br/><br/>

<!-- Should not fail if the two links have the same link target -->
<div>
<a style="display: block; width: 10px; height: 10px;" href="../seo/"></a>
<a style="display: block; width: 10px; height: 10px;" href="./"></a>
<a
data-gathered-target="links-with-same-link-target-1"
style="display: block; width: 10px; height: 10px;" href="../seo/"></a>
<a
data-gathered-target="links-with-same-link-target-2"
style="display: block; width: 10px; height: 10px;" href="./"></a>
</div>

<!-- Links in text blocks are exempted from size/overlap requirements and should not fail -->
Expand All @@ -118,12 +177,6 @@ <h1>SEO Tap targets</h1>
This is <a>a link in a text block.</a>
This is <a>a link in a text block.</a>
</p>

<a style="background: #afa; display: block; width: 100%; height: 800px;">
This link is intentionally placed at the bottom of the page, so that when we've scrolled
to the bottom of the page it overlaps with the sticky header. (Should not fail though
because the overlap depends on the current scroll position.)
</a>
</div>
</body>
</html>
98 changes: 81 additions & 17 deletions lighthouse-cli/test/smokehouse/seo/expectations.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,60 @@ function headersParam(headers) {
return new URLSearchParams([['extra_header', headerString]]).toString();
}

const expectedGatheredTapTargets = [
{
snippet: /large-link-at-bottom-of-page/,
},
{
snippet: /visible-target/,
},
{
snippet: /target-with-client-rect-outside-scroll-container/,
},
{
snippet: /link-containing-large-inline-block-element/,
},
{
snippet: /link-next-to-link-containing-large-inline-block-element/,
},
{
snippet: /tap-target-containing-other-tap-targets/,
},
{
snippet: /child-client-rect-hidden-by-overflow-hidden/,
},
{
snippet: /tap-target-next-to-child-client-rect-hidden-by-overflow-hidden/,
},
{
snippet: /child-client-rect-overlapping-other-target/,
shouldFail: true,
},
{
snippet: /tap-target-overlapped-by-other-targets-position-absolute-child-rect/,
shouldFail: true,
},
{
snippet: /position-absolute-tap-target-fully-contained-in-other-target/,
},
{
snippet: /tap-target-fully-containing-position-absolute-target/,
},
{
snippet: /too-small-failing-tap-target/,
shouldFail: true,
},
{
snippet: /large-enough-tap-target-next-to-too-small-tap-target/,
},
{
snippet: /links-with-same-link-target-1/,
},
{
snippet: /links-with-same-link-target-2/,
},
];

const failureHeaders = headersParam([[
'x-robots-tag',
'none',
Expand Down Expand Up @@ -190,48 +244,58 @@ module.exports = [
audits: {
'tap-targets': {
score: (() => {
const PASSING_TAP_TARGETS = 11;
const TOTAL_TAP_TARGETS = 12;
const totalTapTargets = expectedGatheredTapTargets.length;
const passingTapTargets = expectedGatheredTapTargets.filter(t => !t.shouldFail).length;
const SCORE_FACTOR = 0.89;
return Math.floor(PASSING_TAP_TARGETS / TOTAL_TAP_TARGETS * SCORE_FACTOR * 100) / 100;
return Math.round(passingTapTargets / totalTapTargets * SCORE_FACTOR * 100) / 100;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a tad worried by how much logic ends up living in our expectations, but I sympathize with the tedium of updating these and would rather have it be more precise so 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a tad worried by how much logic ends up living in our expectations, but I sympathize with the tedium of updating these and would rather have it be more precise so 👍

I think this might be going too far in this direction :/ It's one thing when it's broken down but still defined by constants, but since this is pulling live from the test file, it would be difficult to notice e.g. if we broke one of the overlapping tap target examples in the test page. So long as it also no longer matched the regex it would still pass and we wouldn't notice we were losing coverage.

It's also nice to have a place we can point to with "here's what we expect a failing/real value for this audit to look like", and with this approach the only way to do that is to put in a breakpoint and open up a debugger.

})(),
details: {
items: [
{
'tapTarget': {
'type': 'node',
'snippet': '<a ' +
'style="display: block; width: 100px; height: 30px;background: #ddd;">' +
'\n too small target\n </a>',
'path': '2,HTML,1,BODY,3,DIV,21,DIV,0,A',
'path': '2,HTML,1,BODY,10,DIV,0,DIV,1,A',
'selector': 'body > div > div > a',
'nodeLabel': 'too small target',
},
'overlappingTarget': {
'type': 'node',
'snippet': '<a ' +
'style="display: block; width: 100px; height: 100px;background: #aaa;">' +
'\n big enough target\n </a>',
'path': '2,HTML,1,BODY,3,DIV,21,DIV,1,A',
'path': '2,HTML,1,BODY,10,DIV,0,DIV,2,A',
'selector': 'body > div > div > a',
'nodeLabel': 'big enough target',
},
'size': '100x30',
'width': 100,
'height': 30,
'tapTargetScore': 1440,
'overlappingTargetScore': 432,
'overlapScoreRatio': 0.3,
'size': '100x30',
'width': 100,
'height': 30,
},
{
'tapTarget': {
'type': 'node',
'path': '2,HTML,1,BODY,3,DIV,24,A',
'selector': 'body > div > a',
},
'overlappingTarget': {
'type': 'node',
'path': '2,HTML,1,BODY,3,DIV,25,A',
'selector': 'body > div > a',
},
'tapTargetScore': 1920,
'overlappingTargetScore': 560,
'overlapScoreRatio': 0.2916666666666667,
'size': '40x40',
'width': 40,
'height': 40,
},
],
},
},
},
},
artifacts: {
TapTargets: {
length: 11,
},
TapTargets: expectedGatheredTapTargets.map(({snippet}) => ({snippet})),
},
},
];
Loading