From c9d0c1b879f70bfbe5f1276353dca37466b0116a Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Fri, 1 Sep 2017 16:50:47 +0200 Subject: [PATCH 1/7] SEO description audit - WIP --- lighthouse-core/audits/seo/description.js | 38 +++++++++++++++++++ lighthouse-core/config/seo.js | 19 +++++++++- .../gather/gatherers/seo/description.js | 24 ++++++++++++ lighthouse-core/runner.js | 2 + 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 lighthouse-core/audits/seo/description.js create mode 100644 lighthouse-core/gather/gatherers/seo/description.js diff --git a/lighthouse-core/audits/seo/description.js b/lighthouse-core/audits/seo/description.js new file mode 100644 index 000000000000..29e0c46a55e1 --- /dev/null +++ b/lighthouse-core/audits/seo/description.js @@ -0,0 +1,38 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const Audit = require('../audit'); + +class Description extends Audit { + /** + * @return {!AuditMeta} + */ + static get meta() { + return { + category: 'Content Best Practices', + name: 'meta-description', + description: 'Document has a meta description', + failureDescription: 'Document does not have a meta description', + helpText: 'Meta descriptions may be included in search results to concisely summarize ' + + 'page content. Read more in the ' + + '[Search Console Help page](https://support.google.com/webmasters/answer/35624?hl=en#1).', + requiredArtifacts: ['Description'] + }; + } + + /** + * @param {!Artifacts} artifacts + * @return {!AuditResult} + */ + static audit(artifacts) { + return { + rawValue: (artifacts.Description !== null && artifacts.Description.length) + }; + } +} + +module.exports = Description; diff --git a/lighthouse-core/config/seo.js b/lighthouse-core/config/seo.js index 2e86f38ba18c..180c7dd058da 100644 --- a/lighthouse-core/config/seo.js +++ b/lighthouse-core/config/seo.js @@ -7,6 +7,20 @@ module.exports = { extends: 'lighthouse:default', + passes: [{ + passName: 'seoPass', + recordTrace: true, + pauseAfterLoadMs: 5250, + networkQuietThresholdMs: 5250, + cpuQuietThresholdMs: 5250, + useThrottling: true, + gatherers: [ + 'seo/description', + ] + }], + audits: [ + 'seo/description' + ], groups: { 'seo-mobile': { title: 'Mobile Friendly', @@ -28,8 +42,9 @@ module.exports = { description: 'These ensure your app is able to be understood by search engine crawlers.', audits: [ {id: 'meta-viewport', weight: 1, group: 'seo-mobile'}, - {id: 'document-title', weight: 1, group: 'seo-content'} + {id: 'document-title', weight: 1, group: 'seo-content'}, + {id: 'meta-description', weight: 1, group: 'seo-content'} ] } - } + }, }; diff --git a/lighthouse-core/gather/gatherers/seo/description.js b/lighthouse-core/gather/gatherers/seo/description.js new file mode 100644 index 000000000000..c8595931bdb9 --- /dev/null +++ b/lighthouse-core/gather/gatherers/seo/description.js @@ -0,0 +1,24 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const Gatherer = require('../gatherer'); + +class Description extends Gatherer { + + /** + * @param {{driver: !Object}} options Run options + * @return {!Promise} The value of the description meta's content attribute, or null + */ + afterPass(options) { + const driver = options.driver; + + return driver.querySelector('head meta[name="description"]') + .then(node => node && node.getAttribute('content')); + } +} + +module.exports = Description; diff --git a/lighthouse-core/runner.js b/lighthouse-core/runner.js index feb4b85e5e80..eaea65bcae23 100644 --- a/lighthouse-core/runner.js +++ b/lighthouse-core/runner.js @@ -227,6 +227,7 @@ class Runner { const fileList = [ ...fs.readdirSync(path.join(__dirname, './audits')), ...fs.readdirSync(path.join(__dirname, './audits/dobetterweb')).map(f => `dobetterweb/${f}`), + ...fs.readdirSync(path.join(__dirname, './audits/seo')).map(f => `seo/${f}`), ...fs.readdirSync(path.join(__dirname, './audits/accessibility')) .map(f => `accessibility/${f}`), ...fs.readdirSync(path.join(__dirname, './audits/byte-efficiency')) @@ -245,6 +246,7 @@ class Runner { static getGathererList() { const fileList = [ ...fs.readdirSync(path.join(__dirname, './gather/gatherers')), + ...fs.readdirSync(path.join(__dirname, './gather/gatherers/seo')).map(f => `seo/${f}`), ...fs.readdirSync(path.join(__dirname, './gather/gatherers/dobetterweb')) .map(f => `dobetterweb/${f}`) ]; From ce43b7945669a5dd51832f7c87ddf8736c272289 Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Fri, 1 Sep 2017 22:01:32 +0200 Subject: [PATCH 2/7] Add tests, fix audit, rename audit file --- .../{description.js => meta-description.js} | 2 +- lighthouse-core/config/seo.js | 9 ++--- .../test/audits/seo/meta-description-test.js | 31 +++++++++++++++++ .../gather/gatherers/seo/description-test.js | 34 +++++++++++++++++++ 4 files changed, 68 insertions(+), 8 deletions(-) rename lighthouse-core/audits/seo/{description.js => meta-description.js} (98%) create mode 100644 lighthouse-core/test/audits/seo/meta-description-test.js create mode 100644 lighthouse-core/test/gather/gatherers/seo/description-test.js diff --git a/lighthouse-core/audits/seo/description.js b/lighthouse-core/audits/seo/meta-description.js similarity index 98% rename from lighthouse-core/audits/seo/description.js rename to lighthouse-core/audits/seo/meta-description.js index 29e0c46a55e1..0cfeaf7998b9 100644 --- a/lighthouse-core/audits/seo/description.js +++ b/lighthouse-core/audits/seo/meta-description.js @@ -30,7 +30,7 @@ class Description extends Audit { */ static audit(artifacts) { return { - rawValue: (artifacts.Description !== null && artifacts.Description.length) + rawValue: (artifacts.Description !== null && artifacts.Description.length > 0) }; } } diff --git a/lighthouse-core/config/seo.js b/lighthouse-core/config/seo.js index 180c7dd058da..b46d19f546e2 100644 --- a/lighthouse-core/config/seo.js +++ b/lighthouse-core/config/seo.js @@ -9,17 +9,12 @@ module.exports = { extends: 'lighthouse:default', passes: [{ passName: 'seoPass', - recordTrace: true, - pauseAfterLoadMs: 5250, - networkQuietThresholdMs: 5250, - cpuQuietThresholdMs: 5250, - useThrottling: true, gatherers: [ 'seo/description', ] }], audits: [ - 'seo/description' + 'seo/meta-description' ], groups: { 'seo-mobile': { @@ -46,5 +41,5 @@ module.exports = { {id: 'meta-description', weight: 1, group: 'seo-content'} ] } - }, + } }; diff --git a/lighthouse-core/test/audits/seo/meta-description-test.js b/lighthouse-core/test/audits/seo/meta-description-test.js new file mode 100644 index 000000000000..7201ce495e2f --- /dev/null +++ b/lighthouse-core/test/audits/seo/meta-description-test.js @@ -0,0 +1,31 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const Audit = require('../../../audits/seo/meta-description.js'); +const assert = require('assert'); + +/* eslint-env mocha */ + +describe('SEO: description audit', () => { + it('fails when HTML does not contain a description meta tag', () => { + return assert.equal(Audit.audit({ + Description: null + }).rawValue, false); + }); + + it('fails when HTML contains an empty description meta tag', () => { + return assert.equal(Audit.audit({ + Description: '' + }).rawValue, false); + }); + + it('passes when a description text is provided', () => { + return assert.equal(Audit.audit({ + Description: 'description text' + }).rawValue, true); + }); +}); diff --git a/lighthouse-core/test/gather/gatherers/seo/description-test.js b/lighthouse-core/test/gather/gatherers/seo/description-test.js new file mode 100644 index 000000000000..354367ae71b6 --- /dev/null +++ b/lighthouse-core/test/gather/gatherers/seo/description-test.js @@ -0,0 +1,34 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +/* eslint-env mocha */ + +const DescriptionGather = require('../../../../gather/gatherers/seo/description'); +const assert = require('assert'); +const testDescription = 'description text'; +let descriptionGather; + +describe('Meta description gatherer', () => { + // Reset the Gatherer before each test. + beforeEach(() => { + descriptionGather = new DescriptionGather(); + }); + + it('returns an artifact', () => { + return descriptionGather.afterPass({ + driver: { + querySelector() { + return Promise.resolve({ + getAttribute: () => testDescription + }); + } + } + }).then(artifact => { + assert.equal(artifact, testDescription); + }); + }); +}); From 9aa4cc278defb91cdb40d2595d149d9bf06941ab Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Fri, 1 Sep 2017 22:34:21 +0200 Subject: [PATCH 3/7] Use CAPITALIZED_WITH_UNDERSCORES instead of CamelCase for a test constant --- .../test/gather/gatherers/seo/description-test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lighthouse-core/test/gather/gatherers/seo/description-test.js b/lighthouse-core/test/gather/gatherers/seo/description-test.js index 354367ae71b6..188ac1bfb28f 100644 --- a/lighthouse-core/test/gather/gatherers/seo/description-test.js +++ b/lighthouse-core/test/gather/gatherers/seo/description-test.js @@ -9,7 +9,7 @@ const DescriptionGather = require('../../../../gather/gatherers/seo/description'); const assert = require('assert'); -const testDescription = 'description text'; +const EXAMPLE_DESCRIPTION = 'description text'; let descriptionGather; describe('Meta description gatherer', () => { @@ -23,12 +23,12 @@ describe('Meta description gatherer', () => { driver: { querySelector() { return Promise.resolve({ - getAttribute: () => testDescription + getAttribute: () => EXAMPLE_DESCRIPTION }); } } }).then(artifact => { - assert.equal(artifact, testDescription); + assert.equal(artifact, EXAMPLE_DESCRIPTION); }); }); }); From dafe4aeaebd4d406c6b3da80b5ace2a76087641a Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Fri, 1 Sep 2017 23:09:22 +0200 Subject: [PATCH 4/7] Extend defaultPass instead of creating a new one. Rename Description artifact to MetaArtifact (and update gatherer file name accordingly) --- lighthouse-core/audits/seo/meta-description.js | 4 ++-- lighthouse-core/config/seo.js | 6 +++--- .../gatherers/seo/{description.js => meta-description.js} | 5 +++-- lighthouse-core/test/audits/seo/meta-description-test.js | 6 +++--- .../seo/{description-test.js => meta-description-test.js} | 8 ++++---- 5 files changed, 15 insertions(+), 14 deletions(-) rename lighthouse-core/gather/gatherers/seo/{description.js => meta-description.js} (92%) rename lighthouse-core/test/gather/gatherers/seo/{description-test.js => meta-description-test.js} (83%) diff --git a/lighthouse-core/audits/seo/meta-description.js b/lighthouse-core/audits/seo/meta-description.js index 0cfeaf7998b9..cbaad4c640d5 100644 --- a/lighthouse-core/audits/seo/meta-description.js +++ b/lighthouse-core/audits/seo/meta-description.js @@ -20,7 +20,7 @@ class Description extends Audit { helpText: 'Meta descriptions may be included in search results to concisely summarize ' + 'page content. Read more in the ' + '[Search Console Help page](https://support.google.com/webmasters/answer/35624?hl=en#1).', - requiredArtifacts: ['Description'] + requiredArtifacts: ['MetaDescription'] }; } @@ -30,7 +30,7 @@ class Description extends Audit { */ static audit(artifacts) { return { - rawValue: (artifacts.Description !== null && artifacts.Description.length > 0) + rawValue: (artifacts.MetaDescription !== null && artifacts.MetaDescription.length > 0) }; } } diff --git a/lighthouse-core/config/seo.js b/lighthouse-core/config/seo.js index b46d19f546e2..613878bba9e1 100644 --- a/lighthouse-core/config/seo.js +++ b/lighthouse-core/config/seo.js @@ -8,13 +8,13 @@ module.exports = { extends: 'lighthouse:default', passes: [{ - passName: 'seoPass', + passName: 'defaultPass', gatherers: [ - 'seo/description', + 'seo/meta-description', ] }], audits: [ - 'seo/meta-description' + 'seo/meta-description', ], groups: { 'seo-mobile': { diff --git a/lighthouse-core/gather/gatherers/seo/description.js b/lighthouse-core/gather/gatherers/seo/meta-description.js similarity index 92% rename from lighthouse-core/gather/gatherers/seo/description.js rename to lighthouse-core/gather/gatherers/seo/meta-description.js index c8595931bdb9..afe96800a083 100644 --- a/lighthouse-core/gather/gatherers/seo/description.js +++ b/lighthouse-core/gather/gatherers/seo/meta-description.js @@ -7,7 +7,7 @@ const Gatherer = require('../gatherer'); -class Description extends Gatherer { +class MetaDescription extends Gatherer { /** * @param {{driver: !Object}} options Run options @@ -21,4 +21,5 @@ class Description extends Gatherer { } } -module.exports = Description; +module.exports = MetaDescription; + diff --git a/lighthouse-core/test/audits/seo/meta-description-test.js b/lighthouse-core/test/audits/seo/meta-description-test.js index 7201ce495e2f..ce55d221d3e7 100644 --- a/lighthouse-core/test/audits/seo/meta-description-test.js +++ b/lighthouse-core/test/audits/seo/meta-description-test.js @@ -13,19 +13,19 @@ const assert = require('assert'); describe('SEO: description audit', () => { it('fails when HTML does not contain a description meta tag', () => { return assert.equal(Audit.audit({ - Description: null + MetaDescription: null }).rawValue, false); }); it('fails when HTML contains an empty description meta tag', () => { return assert.equal(Audit.audit({ - Description: '' + MetaDescription: '' }).rawValue, false); }); it('passes when a description text is provided', () => { return assert.equal(Audit.audit({ - Description: 'description text' + MetaDescription: 'description text' }).rawValue, true); }); }); diff --git a/lighthouse-core/test/gather/gatherers/seo/description-test.js b/lighthouse-core/test/gather/gatherers/seo/meta-description-test.js similarity index 83% rename from lighthouse-core/test/gather/gatherers/seo/description-test.js rename to lighthouse-core/test/gather/gatherers/seo/meta-description-test.js index 188ac1bfb28f..c44f2f25344e 100644 --- a/lighthouse-core/test/gather/gatherers/seo/description-test.js +++ b/lighthouse-core/test/gather/gatherers/seo/meta-description-test.js @@ -7,19 +7,19 @@ /* eslint-env mocha */ -const DescriptionGather = require('../../../../gather/gatherers/seo/description'); +const MetaDescriptionGather = require('../../../../gather/gatherers/seo/meta-description'); const assert = require('assert'); const EXAMPLE_DESCRIPTION = 'description text'; -let descriptionGather; +let metaDescriptionGather; describe('Meta description gatherer', () => { // Reset the Gatherer before each test. beforeEach(() => { - descriptionGather = new DescriptionGather(); + metaDescriptionGather = new MetaDescriptionGather(); }); it('returns an artifact', () => { - return descriptionGather.afterPass({ + return metaDescriptionGather.afterPass({ driver: { querySelector() { return Promise.resolve({ From 969143e001e816c71e273860ad12aa773435b309 Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Sat, 2 Sep 2017 23:45:59 +0200 Subject: [PATCH 5/7] Trim the description before checking length. Add debugString's. --- lighthouse-core/audits/seo/meta-description.js | 16 +++++++++++++++- .../test/audits/seo/meta-description-test.js | 6 ++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lighthouse-core/audits/seo/meta-description.js b/lighthouse-core/audits/seo/meta-description.js index cbaad4c640d5..6cf52a7b7f84 100644 --- a/lighthouse-core/audits/seo/meta-description.js +++ b/lighthouse-core/audits/seo/meta-description.js @@ -29,8 +29,22 @@ class Description extends Audit { * @return {!AuditResult} */ static audit(artifacts) { + if (artifacts.MetaDescription === null) { + return { + rawValue: false, + debugString: 'Description meta tag is missing.' + }; + } + + if (artifacts.MetaDescription.trim().length === 0) { + return { + rawValue: false, + debugString: 'Description text is empty.' + }; + } + return { - rawValue: (artifacts.MetaDescription !== null && artifacts.MetaDescription.length > 0) + rawValue: true }; } } diff --git a/lighthouse-core/test/audits/seo/meta-description-test.js b/lighthouse-core/test/audits/seo/meta-description-test.js index ce55d221d3e7..373c94953765 100644 --- a/lighthouse-core/test/audits/seo/meta-description-test.js +++ b/lighthouse-core/test/audits/seo/meta-description-test.js @@ -23,6 +23,12 @@ describe('SEO: description audit', () => { }).rawValue, false); }); + it('fails when description consists only of whitespace', () => { + return assert.equal(Audit.audit({ + MetaDescription: ' ' + }).rawValue, false); + }); + it('passes when a description text is provided', () => { return assert.equal(Audit.audit({ MetaDescription: 'description text' From e2302f09cfb9772c051c5caee190af2aad2547ef Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Fri, 8 Sep 2017 16:02:43 +0200 Subject: [PATCH 6/7] Test if failing audits return a debugString. --- .../test/audits/seo/meta-description-test.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lighthouse-core/test/audits/seo/meta-description-test.js b/lighthouse-core/test/audits/seo/meta-description-test.js index 373c94953765..4bc6833eb4e1 100644 --- a/lighthouse-core/test/audits/seo/meta-description-test.js +++ b/lighthouse-core/test/audits/seo/meta-description-test.js @@ -12,21 +12,27 @@ const assert = require('assert'); describe('SEO: description audit', () => { it('fails when HTML does not contain a description meta tag', () => { - return assert.equal(Audit.audit({ + const auditResult = Audit.audit({ MetaDescription: null - }).rawValue, false); + }); + assert.equal(auditResult.rawValue, false); + assert.ok(auditResult.debugString); }); it('fails when HTML contains an empty description meta tag', () => { - return assert.equal(Audit.audit({ + const auditResult = Audit.audit({ MetaDescription: '' - }).rawValue, false); + }); + assert.equal(auditResult.rawValue, false); + assert.ok(auditResult.debugString); }); it('fails when description consists only of whitespace', () => { - return assert.equal(Audit.audit({ - MetaDescription: ' ' - }).rawValue, false); + const auditResult = Audit.audit({ + MetaDescription: '  ' + }); + assert.equal(auditResult.rawValue, false); + assert.ok(auditResult.debugString); }); it('passes when a description text is provided', () => { From 255196fcbbf218858638aa84762e89269a2cb2f0 Mon Sep 17 00:00:00 2001 From: Konrad Dzwinel Date: Sat, 9 Sep 2017 23:21:03 +0200 Subject: [PATCH 7/7] No debugString when tag is missing, better tests, trailing comma. --- lighthouse-core/audits/seo/meta-description.js | 3 +-- lighthouse-core/config/seo.js | 2 +- lighthouse-core/test/audits/seo/meta-description-test.js | 5 ++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lighthouse-core/audits/seo/meta-description.js b/lighthouse-core/audits/seo/meta-description.js index 6cf52a7b7f84..c4c11b28ece9 100644 --- a/lighthouse-core/audits/seo/meta-description.js +++ b/lighthouse-core/audits/seo/meta-description.js @@ -31,8 +31,7 @@ class Description extends Audit { static audit(artifacts) { if (artifacts.MetaDescription === null) { return { - rawValue: false, - debugString: 'Description meta tag is missing.' + rawValue: false }; } diff --git a/lighthouse-core/config/seo.js b/lighthouse-core/config/seo.js index 613878bba9e1..0a45e8168c6c 100644 --- a/lighthouse-core/config/seo.js +++ b/lighthouse-core/config/seo.js @@ -38,7 +38,7 @@ module.exports = { audits: [ {id: 'meta-viewport', weight: 1, group: 'seo-mobile'}, {id: 'document-title', weight: 1, group: 'seo-content'}, - {id: 'meta-description', weight: 1, group: 'seo-content'} + {id: 'meta-description', weight: 1, group: 'seo-content'}, ] } } diff --git a/lighthouse-core/test/audits/seo/meta-description-test.js b/lighthouse-core/test/audits/seo/meta-description-test.js index 4bc6833eb4e1..6a18badfbf04 100644 --- a/lighthouse-core/test/audits/seo/meta-description-test.js +++ b/lighthouse-core/test/audits/seo/meta-description-test.js @@ -16,7 +16,6 @@ describe('SEO: description audit', () => { MetaDescription: null }); assert.equal(auditResult.rawValue, false); - assert.ok(auditResult.debugString); }); it('fails when HTML contains an empty description meta tag', () => { @@ -24,7 +23,7 @@ describe('SEO: description audit', () => { MetaDescription: '' }); assert.equal(auditResult.rawValue, false); - assert.ok(auditResult.debugString); + assert.ok(auditResult.debugString.includes('empty'), auditResult.debugString); }); it('fails when description consists only of whitespace', () => { @@ -32,7 +31,7 @@ describe('SEO: description audit', () => { MetaDescription: '  ' }); assert.equal(auditResult.rawValue, false); - assert.ok(auditResult.debugString); + assert.ok(auditResult.debugString.includes('empty'), auditResult.debugString); }); it('passes when a description text is provided', () => {