Skip to content
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

core(installable-manifest): use devtools InstallabilityErrors #11745

Merged
merged 48 commits into from
Dec 15, 2020

Conversation

adrianaixba
Copy link
Collaborator

@adrianaixba adrianaixba commented Dec 2, 2020

Use devtools InstallabilityErrors, to reduce the variability between devtools and the lighthouse report.

fixes #10515 #10237

Follow ups:

Future action:

@google-cla google-cla bot added the cla: yes label Dec 2, 2020
@adrianaixba adrianaixba changed the title core(installable-manifest): replace installability checks with devtools InstallabilityErrors core(installable-manifest): use devtools InstallabilityErrors Dec 2, 2020
@connorjclark
Copy link
Collaborator

Since this is in draft state, can you specify which parts you'd like feedback on, and what things you already know needs more work?

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

a few comments on some things that immediately jump out at me :)

score: 0,
details: {items: [jakeExpectations]},
explanation: /^Failures: .*short_name/,
score: 1,
Copy link
Member

Choose a reason for hiding this comment

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

does this mean "Manifest does not contain a 'name' or 'short_name' field" only requires one of name and short_name, so is passing now?

Copy link
Member

Choose a reason for hiding this comment

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

😆

a coupla weeks ago i spent a while looking at https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/installable/installable_manager.cc;l=651-655;drc=05dff50d9ce188e04ef796f0f1c28fbef09ace59 and my brain still doesn't make sense of it.

it seeems to say:

if (nameIsMissingOrEmpty && shortNameIsMissingOrEmpty) {
  errors.push('MANIFEST_MISSING_NAME_OR_SHORT_NAME')
}

and how does && in the logic match up with a var named OR.

in fact it seemed so wrong that i figured it might be a bug. so adriana and I made a CL: https://chromium-review.googlesource.com/c/chromium/src/+/2530481
but the tests are all failureful.

so tbh i have no idea.

Copy link
Collaborator

@patrickhulce patrickhulce Dec 14, 2020

Choose a reason for hiding this comment

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

The fact that this makes sense to me now has me worried about my understanding... 😆

I think the MANIFEST_MISSING_NAME_OR_SHORT_NAME is just ambiguously named because when interpreting it as "manifest missing something - name or short name needed", which is consistent with my understanding that you need something to use as the name, is all works out. You definitely don't need both name and short_name.

It's not the case for installability that you need a short_name, right? I believe Lighthouse just did so because we thought it was a good idea for most apps, but now that we've switched I would expect the && to fail the tests and our result to change.

lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
/** Error message explaining that a URL in the manifest contains a username, password, or port. */
'url-not-supported-for-webapk': `A URL in the manifest contains a username, password, or port`,
/** Error message explaining that the page is loaded in an incognito window. */
'in-incognito': `Page is loaded in an incognito window`,
Copy link
Member

Choose a reason for hiding this comment

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

we recommend users with busy extensions or important storage use incognito to test. Does this mean PWA testing won't work there?

Copy link
Member

Choose a reason for hiding this comment

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

yes this is accurate.

image

:/

we could choose to special-case this one...

we probably are special casing this "warn" error that's landing soon: https://chromium-review.googlesource.com/c/chromium/src/+/2567057

for (const err of installabilityErrors) {
// @ts-expect-error errorIds from protocol should match up against the strings dict
const matchingString = UIStrings[err.errorId];
// We only expect a `minimum-icon-size-in-pixels` errorArg[0] for two errorIds, currently.
Copy link
Member

Choose a reason for hiding this comment

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

this will throw if this statement becomes not true at some point in the future (new error type or whatever), so better to check this as well :)

Copy link
Member

Choose a reason for hiding this comment

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

yeaaaaah true

Copy link
Collaborator

Choose a reason for hiding this comment

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

a brand new error id that our code doesn't handle will fall into L157.

an existing error id that our code handles could have an arg added to it, and we'd fall into L164 but the error message string wouldn't handle the values. i18n will throw. https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-core/lib/i18n/i18n.js#L267

we could avoid errors if _str somehow provided how many arguments a formatted string has, and check if that is equal to errorArguments.length, and if not we fallback to a generic string (${errorId} ${errorArguments.join(', ')}). But _str doesn't provide # of args. We could do a string search for value# for each index in errorArguments as a substitute ...

Copy link
Collaborator Author

@adrianaixba adrianaixba Dec 8, 2020

Choose a reason for hiding this comment

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

@connorjclark What do you think about having something like this?

const errorArgumentsRegex = /{([^}]+)}/g;
const UIStringArguments = matchingString.match(errorArgumentsRegex) || [];
if (matchingString && value0) {
        errorMessages.push(str_(matchingString, {value0}));
        } else if (matchingString && err.errorArguments.length !== UIStringArguments.length) {
        // Matching string, but have the incorrect number of arguments for the message.
        errorMessages.push(str_(UIStrings.notEnoughArguments, {errorId: err.errorId}))
      } else if (matchingString) {
        errorMessages.push(str_(matchingString));
      } else {
        errorMessages.push(str_(UIStrings.noErrorId));
      }

Where the notEnoughArguments string would be the generic string you mentioned. Although it doesn't include the args in the string. Somewhat similar to how Devtools does it

Copy link
Collaborator

@connorjclark connorjclark Dec 8, 2020

Choose a reason for hiding this comment

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

looks good, that's a tricky regex. couple things

  1. the first case is still susceptible to an error (if we get an unexpected number of args for an error we already handle).
  2. the check for matchingString && err.errorArguments.length !== UIStringArguments.length should go first, that'd solve thing 1)
  3. let's add a second arg to UIStrings.notEnoughArguments, so the string can attempt to be useful (at the risk of printing randomish data). I wonder if we even should use i18n here. @exterkamp the string would be ${errorId} ${errorArguments.join(', ') ... any point to send that to translators?

I suspect the "1 arg case" is fine forever, even if a "2 arg case" comes up still just duplicating a little code instead of making a generic "n arg case" is much better to read.

Copy link
Collaborator

Choose a reason for hiding this comment

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

shane says let's avoid UI strings/i18n for the "notEnoughArguments" case

lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
/** Error message explaining that a URL in the manifest contains a username, password, or port. */
'url-not-supported-for-webapk': `A URL in the manifest contains a username, password, or port`,
/** Error message explaining that the page is loaded in an incognito window. */
'in-incognito': `Page is loaded in an incognito window`,
Copy link
Member

Choose a reason for hiding this comment

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

yes this is accurate.

image

:/

we could choose to special-case this one...

we probably are special casing this "warn" error that's landing soon: https://chromium-review.googlesource.com/c/chromium/src/+/2567057

for (const err of installabilityErrors) {
// @ts-expect-error errorIds from protocol should match up against the strings dict
const matchingString = UIStrings[err.errorId];
// We only expect a `minimum-icon-size-in-pixels` errorArg[0] for two errorIds, currently.
Copy link
Member

Choose a reason for hiding this comment

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

yeaaaaah true

lighthouse-core/lib/i18n/i18n.js Outdated Show resolved Hide resolved
lighthouse-core/test/results/sample_v2.json Outdated Show resolved Hide resolved
score: 0,
details: {items: [jakeExpectations]},
explanation: /^Failures: .*short_name/,
score: 1,
Copy link
Member

Choose a reason for hiding this comment

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

😆

a coupla weeks ago i spent a while looking at https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/installable/installable_manager.cc;l=651-655;drc=05dff50d9ce188e04ef796f0f1c28fbef09ace59 and my brain still doesn't make sense of it.

it seeems to say:

if (nameIsMissingOrEmpty && shortNameIsMissingOrEmpty) {
  errors.push('MANIFEST_MISSING_NAME_OR_SHORT_NAME')
}

and how does && in the logic match up with a var named OR.

in fact it seemed so wrong that i figured it might be a bug. so adriana and I made a CL: https://chromium-review.googlesource.com/c/chromium/src/+/2530481
but the tests are all failureful.

so tbh i have no idea.

@patrickhulce
Copy link
Collaborator

@adrianaixba is the status of this PR correct, awaiting a re-review from @paulirish ?

@adrianaixba
Copy link
Collaborator Author

adrianaixba commented Dec 14, 2020

@adrianaixba is the status of this PR correct, awaiting a re-review from @paulirish ?

One more pass would be nice :)

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

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

lg!

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

sorry, last things :)

let matchingString;
try {
// @ts-expect-error errorIds from protocol should match up against the strings dict
matchingString = UIStrings[err.errorId];
Copy link
Member

Choose a reason for hiding this comment

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

This line is a potential typescript error, but it won't ever throw at runtime (it'll just be undefined). Can just do an if (matchingString === undefined) { check (though the compiler may complain about that too)

this doesn't seem like it got changed?

@adrianaixba
Copy link
Collaborator Author

is this easy to add to the non-offline smoke tests sites, too?

Created new issue for this #11826 :)

artifacts.WebAppManifest = null;
const context = generateMockAuditContext();

return InstallableManifestAudit.audit(artifacts, context).then(result => {
assert.strictEqual(result.score, 0);
assert.ok(result.explanation.includes('No manifest was fetched'), result.explanation);
const items = result.details.items;
assert.ok(items[0].reason.formattedDefault.includes('no manifest'));
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
assert.ok(items[0].reason.formattedDefault.includes('no manifest'));
expect(items[0].reason).toBeDisplayString(/no manifest/);

this is the "proper" way to test the localized version of the string (or i18n.getFormatted(items[0].reason, 'en-US').includes('no manifest')) Mostly the same in practice and in theory, but

  • doesn't reach into the internal representation (allowing for easier i18n refactors in the future) and
  • is testing the final string coming out of the i18n pipeline, so it's closer to what will be in the actual report (formattedDefault is the fallback in case that pipeline fails)

mainly I expect people will be copy/pasting from these tests in the future so it's good to do it the slightly better way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is very clean, i like it 👀

lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
@@ -35,12 +35,14 @@ describe('PWA: webapp install banner audit', () => {
describe('basics', () => {
Copy link
Member

Choose a reason for hiding this comment

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

really a lot of these tests could be dropped now since most of them are just testing are errorIds mapped to strings in the items, but there's not any harm in leaving them in place

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

a few more things, mainly still just test coverage

description: 'Browsers can proactively prompt users to add your app to their homescreen, ' +
'which can lead to higher engagement. ' +
'[Learn more](https://web.dev/installable-manifest/).',
'description': `Service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. With proper Service Worker and manifest implementations, browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://web.dev/installable-manifest/).`,
Copy link
Member

@brendankenny brendankenny Dec 15, 2020

Choose a reason for hiding this comment

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

Suggested change
'description': `Service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. With proper Service Worker and manifest implementations, browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://web.dev/installable-manifest/).`,
'description': `Service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. With proper service worker and manifest implementations, browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://web.dev/installable-manifest/).`,

https://web.dev/installable-manifest

we should update the web.dev article soon to include the content from the other service-worker audits

'url-not-supported-for-webapk': `A URL in the manifest contains a username, password, or port`,
/** Error message explaining that the page is loaded in an incognito window. */
'in-incognito': `Page is loaded in an incognito window`,
// TODO: perhaps edit this message to make it more actionable for LH users
Copy link
Member

Choose a reason for hiding this comment

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

This will get lost in here, so should this be filed as an issue or is it covered enough now that it could be removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done! #11830 Yeah these are messages that we might want to communicate with the installability/devtools folks about, to get a clearer definition of for LH

lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
lighthouse-core/audits/installable-manifest.js Outdated Show resolved Hide resolved
lighthouse-core/test/audits/installable-manifest-test.js Outdated Show resolved Hide resolved
Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

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

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Use installabilityErrors for PWA Installable criteria
7 participants