testilo 3.9.14 → 3.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testilo",
3
- "version": "3.9.14",
3
+ "version": "3.10.0",
4
4
  "description": "Client that scores and digests Testaro reports",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -18,9 +18,10 @@
18
18
  <h1>Transactional accessibility comparison</h1>
19
19
  </header>
20
20
  <h2>Introduction</h2>
21
- <p>This report compares __pageCount__ web pages, rating each page on <dfn>transactional accessibility</dfn>. The score given to each page estimates the <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a> of one particular transaction, in which the user discovers how to report an accessibility issue with the page to those who are responsible for the page.</p>
22
- <p>You can find a more detailed explanation of transactional accessibility and how the accessibility of this particular transaction is scored in each of the reports. The scores in the table below are links to those reports.</p>
23
- <p>This report was produced by the <code>cpA11yMessage</code> procedure of <a href="https://www.npmjs.com/package/testilo">Testilo</a>.
21
+ <p>This report compares __pageCount__ web pages, rating each page on <dfn>transactional accessibility</dfn>. The score given to each page estimates the <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a> of one particular transaction. In it, the user discovers how to report an accessibility issue with the page to those who are responsible for the page.</p>
22
+ <p>You can find a more detailed explanation of transactional accessibility and how the accessibility of this particular transaction is scored in each of the digests that this report is derived from. The scores in the table below are links to those digests.</p>
23
+ <p>This comparative report was produced by the <code>cpA11yMessage</code> procedure of <a href="https://www.npmjs.com/package/testilo">Testilo</a>.</p>
24
+ <p>The scores in the table below could range from 0 (complete failure) up to 23 (complete success).</p>
24
25
  <h2>Comparison</h2>
25
26
  <table class="allBorder">
26
27
  <caption>Accessibility scores of accessibility-reporting transactions</caption>
@@ -46,7 +46,7 @@ const getMaxScore = tableData => tableData.reduce((max, item) => Math.max(max, i
46
46
  const getTableBody = async bodyData => {
47
47
  const maxScore = getMaxScore(bodyData);
48
48
  const rows = bodyData
49
- .sort((a, b) => a.score - b.score)
49
+ .sort((a, b) => b.score - a.score)
50
50
  .map(item => {
51
51
  const {id, org, url, score} = item;
52
52
  const pageCell = `<th scope="row"><a href="${url}">${org}</a></th>`;
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE HTML>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="author" content="Testilo">
7
+ <meta name="creator" content="Testilo">
8
+ <meta name="publisher" name="Testilo">
9
+ <meta name="description" content="comparison of accessibility scores">
10
+ <meta name="keywords" content="accessibility a11y web testing">
11
+ <title>Accessibility score comparison</title>
12
+ <link rel="icon" href="favicon.png">
13
+ <link rel="stylesheet" href="style.css">
14
+ </head>
15
+ <body>
16
+ <main>
17
+ <header>
18
+ <h1>Transactional accessibility comparison</h1>
19
+ </header>
20
+ <h2>Introduction</h2>
21
+ <p>This report compares __pageCount__ web pages, rating each page on <dfn>transactional accessibility</dfn>. The score given to each page estimates the <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a> of one particular transaction. In it, the user finds the price at which a product is offered for sale on the website to which the page belongs.</p>
22
+ <p>You can find a more detailed explanation of transactional accessibility and how the accessibility of this particular transaction is scored in each of the digests that this report is derived from. The scores in the table below are links to those digests.</p>
23
+ <p>This comparative report was produced by the <code>cpProductPrice</code> procedure of <a href="https://www.npmjs.com/package/testilo">Testilo</a>.</p>
24
+ <p>The scores in the table below could range from 0 (complete failure) up to 26 (complete success).</p>
25
+ <h2>Comparison</h2>
26
+ <table class="allBorder">
27
+ <caption>Accessibility scores of product-price-inquiry transactions</caption>
28
+ <thead>
29
+ <tr><th scope="col">Page</th><th scope="col" colspan="2">Score (higher is better)</tr>
30
+ </thead>
31
+ <tbody class="linkSmaller secondCellRight">
32
+ __tableBody__
33
+ </tbody>
34
+ </table>
35
+ <footer>
36
+ <p class="date">Produced <time itemprop="datePublished" datetime="__dateISO__">__dateSlash__</time></p>
37
+ </footer>
38
+ </main>
39
+ </body>
40
+ </html>
@@ -0,0 +1,71 @@
1
+ /*
2
+ cpProductPrice.js
3
+ Returns a query for an HTML page including a bar-graph table.
4
+ */
5
+
6
+ // ########## IMPORTS
7
+
8
+ // Module to keep secrets local.
9
+ require('dotenv').config();
10
+ // Module to access files.
11
+ const fs = require('fs/promises');
12
+
13
+ // ########## CONSTANTS
14
+
15
+ const reportDirScored = process.env.REPORTDIR_SCORED || 'reports/scored';
16
+ const query = {};
17
+
18
+ // ########## FUNCTIONS
19
+
20
+ // Returns data on the hosts in the report directory.
21
+ const getData = async () => {
22
+ const reportDirAbs = `${__dirname}/../../../${reportDirScored}`;
23
+ const reportFileNamesAll = await fs.readdir(reportDirAbs);
24
+ const reportFileNamesSource = reportFileNamesAll.filter(fileName => fileName.endsWith('.json'));
25
+ const pageCount = reportFileNamesSource.length;
26
+ const bodyData = [];
27
+ for (const fileName of reportFileNamesSource) {
28
+ const fileJSON = await fs.readFile(`${reportDirAbs}/${fileName}`, 'utf8');
29
+ const file = JSON.parse(fileJSON);
30
+ const {id, host, score} = file;
31
+ bodyData.push({
32
+ id,
33
+ org: host.what,
34
+ url: host.which,
35
+ score: score.total
36
+ });
37
+ };
38
+ return {
39
+ pageCount,
40
+ bodyData
41
+ }
42
+ };
43
+ // Returns the maximum score.
44
+ const getMaxScore = tableData => tableData.reduce((max, item) => Math.max(max, item.score), 0);
45
+ // Converts report data to a table body.
46
+ const getTableBody = async bodyData => {
47
+ const maxScore = getMaxScore(bodyData);
48
+ const rows = bodyData
49
+ .sort((a, b) => b.score - a.score)
50
+ .map(item => {
51
+ const {id, org, url, score} = item;
52
+ const pageCell = `<th scope="row"><a href="${url}">${org}</a></th>`;
53
+ const numCell = `<td><a href="digests/${id}.html">${score}</a></td>`;
54
+ const barWidth = 100 * score / maxScore;
55
+ const bar = `<rect height="100%" width="${barWidth}%" fill="red"></rect>`;
56
+ const barCell = `<td aria-hidden="true"><svg width="100%" height="0.7em">${bar}</svg></td>`;
57
+ const row = `<tr>${pageCell}${numCell}${barCell}</tr>`;
58
+ return row;
59
+ });
60
+ return rows.join('\n ');
61
+ };
62
+ // Returns a query for a comparative table.
63
+ exports.getQuery = async () => {
64
+ const data = await getData();
65
+ query.pageCount = data.pageCount;
66
+ query.tableBody = await getTableBody(data.bodyData);
67
+ const date = new Date();
68
+ query.dateISO = date.toISOString().slice(0, 10);
69
+ query.dateSlash = query.dateISO.replace(/-/g, '/');
70
+ return query;
71
+ };
@@ -27,18 +27,18 @@
27
27
  </div>
28
28
  </header>
29
29
  <h2>Introduction</h2>
30
- <p>This is a report on transactional <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a>. Suppose a person is visiting a website and wants to engage in some transaction with it. Is the transaction easy, predictable, and safe? If so, the website is <dfn>transactionally accessible</dfn>. Some disabilities make it difficult to discover how to do things on a website. Everybody, but especially any person with such a disability, benefits from transactions being designed to conform to the most common and standard conventions.</p>
31
- <p>This report deals with an issue-reporting transaction. Specifically, the transaction begins when a person notices an accessibility issue on a page or view of a website or web application and wants to report that issue to whoever is responsible for the website. The simple, standard, and thus predictable transaction is assumed to look like this:</p>
30
+ <p>This is a report on transactional <a href="https://www.w3.org/WAI/fundamentals/accessibility-intro/">accessibility</a>. Suppose a person is visiting a website and wants to engage in some transaction with it. Is the transaction easy, predictable, and safe? If so, the website is <dfn>transactionally accessible</dfn>. To achieve transactional accessibility, websites can design transactions that conform to the most common and standard conventions. Then users will correctly guess what to do at each step. Users with some disabilities will especially benefit from being able to use existing knowledge and tools, without the need to discover how to do things on a website.</p>
31
+ <p>This transaction involves learning how to report a web-page issue. The transaction begins when a person opens a web page and notices an accessibility issue on the page. The user wants to report that issue to whoever is responsible for the website. The test assumes that there is a standard transaction structure in such a situation:</p>
32
32
  <ol>
33
- <li>The person finds an accessibility link on the page.</li>
34
- <li>The person clicks that link.</li>
35
- <li>That link takes the person to an accessibility page.</li>
33
+ <li>The user finds an accessibility link on the page.</li>
34
+ <li>The user clicks that link.</li>
35
+ <li>That link takes the user to an accessibility page.</li>
36
36
  <li>On the accessibility page, there is a link for sending an email message about accessibility, and there is another link for making a telephone call about accessibility.</li>
37
37
  </ol>
38
- <p>Thus, instead of the person needing to figure out how this particular website accepts issue reports, the person is assumed already to know how websites normally accept issue reports. So, instead of exploring the site to find its method, the person uses the standard method.</p>
38
+ <p>Thus, instead of the user needing to figure out how this particular website accepts accessibility issue reports, the user is assumed already to know how websites normally accept such reports. So, instead of exploring the site to find its method, the person uses the standard method.</p>
39
39
  <h2>Procedures</h2>
40
40
  <p>The <a href="https://www.npmjs.com/package/testaro">Testaro</a> application used its <code>tpA11yMessage</code> testing procedure to evaluate the accessibility of this transaction on the __org__ web page at <a href="__url__">__url__</a> on __dateSlash__. Testaro produced a report.</p>
41
- <p>The <a href="https://www.npmjs.com/package/testilo">Testilo</a> application processed the report and used the <code>spA11yMessage</code> scoring procedure to compute a score for the transaction. The total score is __totalScore__ (where 0 is the worst and 16 is the best possible score). The scored report is appended below.</p>
41
+ <p>The <a href="https://www.npmjs.com/package/testilo">Testilo</a> application processed the report and used the <code>spA11yMessage</code> scoring procedure to compute a score for the transaction. The total score is __totalScore__ (where 0 is the worst and 23 is the best possible score). The scored report is appended below.</p>
42
42
  <p>Finally, Testilo used procedure <code>dpA11yMessage</code> to produce this digest, briefly explaining how <code>spA11yMessage</code> computed the scores.</p>
43
43
  <h2>Score summary</h2>
44
44
  <table class="allBorder secondCellRight">
@@ -47,17 +47,28 @@
47
47
  __scoreRows__
48
48
  </tbody>
49
49
  </table>
50
- <h2>Discussion</h2>
50
+ <h2>Explanation</h2>
51
51
  <p>The components of the total score are:</p>
52
52
  <ul>
53
- <li><code>page</code>: Can the page be visited?</li>
54
- <li><code>a11yLink</code>: Does the page have a functioning <q>accessibility</q> link?</li>
55
- <li><code>title</code>: Is the page to which the link goes titled to show it is about accessibility?</li>
56
- <li><code>heading</code>: Does that page (the accessibility page) have a top heading indicating the page is about accessibility?</li>
57
- <li><code>mailLink</code>: Does the accessibility page have a link to send email about accessibility?.</li>
58
- <li><code>telLink</code>: Does the accessibility page have a link to make a telephone call about accessibility?</li>
53
+ <li><code>pageLoad</code>: 2 points if it was possible to visit the page</li>
54
+ <li><code>pageFast</code>: 2 points if the page loaded within 4 seconds, or 1 point if it loaded within 6 seconds</li>
55
+ <li><code>a11yLink</code>: 1 point if the page contained a link whose name included <q>accessibility</q></li>
56
+ <li><code>a11yLinkWork</code>: 2 points if clicking the link produced a new page</li>
57
+ <li><code>a11yLinkFast</code>: 2 points if the new page loaded within 3 seconds, or 1 point if it loaded within 5 seconds</li>
58
+ <li><code>a11yPageTitle</code>: 1 point if the new page had a title</li>
59
+ <li><code>a11yTitleGood</code>: 2 points if the title included <q>accessibility</q></li>
60
+ <li><code>a11yPageH1</code>: 1 point if the new page had 1 level-1 heading</li>
61
+ <li><code>a11yH1Good</code>: 2 points if that heading included <q>accessibility</q></li>
62
+ <li><code>mailLink</code>: 2 points if the new page contained any email (<code>mailto:</code>) link</li>
63
+ <li><code>mailLinkName</code>: 2 points if the name of an email link included <q>accessibility</q>, or 1 point if the context of an email link (i.e. the text content of its parent element) included <q>accessibility</q></li>
64
+ <li><code>telLink</code>: 2 points if the new page contained any telephone (<code>tel:</code>) link</li>
65
+ <li><code>telLinkName</code>: 2 points if the name of a telephone link included <q>accessibility</q>, or 1 point if the context of a telephone link included <q>accessibility</q></li>
59
66
  </ul>
60
67
  <p>The precise rules of <code>spA11yMessage</code> are found in the <a href="https://github.com/jrpool/testilo/blob/main/procs/score/spA11yMessage.js">code itself</a>.</p>
68
+ <h2>Suggestions</h2>
69
+ <ul>
70
+ __suggestions__
71
+ </ul>
61
72
  <h2>Report</h2>
62
73
  <pre>__report__</pre>
63
74
  <footer>
@@ -52,4 +52,89 @@ exports.makeQuery = (report, query) => {
52
52
  });
53
53
  query.totalScore = score.total;
54
54
  query.scoreRows = scoreRows.join(innerJoiner);
55
+ // Add suggestions to the query.
56
+ const suggestions = [];
57
+ if (score.pageLoad === 0) {
58
+ suggestions.push(['pageLoad', 'Make it possible to visit the page.']);
59
+ }
60
+ else {
61
+ if (score.pageFast < 2) {
62
+ suggestions.push(['pageFast', 'Make the page load faster.']);
63
+ }
64
+ if (score.a11yLink === 0) {
65
+ suggestions.push(['a11yLink', 'Add a link named Accessibility to the page.']);
66
+ }
67
+ else {
68
+ if (score.a11yLinkWork === 0) {
69
+ suggestions.push(['a11yLinkWork', 'Make the Accessibility link open a new page.']);
70
+ }
71
+ else {
72
+ if (score.a11yLinkFast < 2) {
73
+ suggestions.push(
74
+ ['a11yLinkFast', 'Make the page opened by the Accessibility link load faster.']
75
+ );
76
+ }
77
+ if (score.a11yPageTitle === 0) {
78
+ suggestions.push(['a11yPageTitle', 'Give the accessibility page a title.']);
79
+ }
80
+ else if (score.a11yTitleGood === 0) {
81
+ suggestions.push(
82
+ ['a11yTitleGood', 'Include accessibility in the title of the accessibility page.']
83
+ )
84
+ }
85
+ if (score.a11yPageH1 === 0) {
86
+ suggestions.push(['a11yPageH1', 'Give the accessibility page a single h1 heading.']);
87
+ }
88
+ else if (score.a11yH1Good === 0) {
89
+ suggestions.push(
90
+ [
91
+ 'a11yH1Good',
92
+ 'Include accessibility in the text of the h1 heading of the accessibility page.'
93
+ ]
94
+ );
95
+ }
96
+ if (score.mailLink === 0) {
97
+ suggestions.push(['mailLink', 'Add an email (mailto:) link to the accessibility page.']);
98
+ }
99
+ else if (score.mailLinkName === 1) {
100
+ suggestions.push(
101
+ [
102
+ 'mailLinkName',
103
+ 'Include accessibility not only around, but within, an email link on the accessibility page.'
104
+ ]
105
+ );
106
+ }
107
+ else if (score.mailLinkName === 0) {
108
+ suggestions.push(
109
+ [
110
+ 'mailLinkName',
111
+ 'Include accessibility in the name of an email link on the accessibility page.'
112
+ ]
113
+ );
114
+ }
115
+ if (score.telLink === 0) {
116
+ suggestions.push(['telLink', 'Add a telephone (tel:) link to the accessibility page.']);
117
+ }
118
+ else if (score.telLinkName === 1) {
119
+ suggestions.push(
120
+ [
121
+ 'telLinkName',
122
+ 'Include accessibility not only around, but within, a telephone link on the accessibility page.'
123
+ ]
124
+ );
125
+ }
126
+ else if (score.telLinkName === 0) {
127
+ suggestions.push(
128
+ [
129
+ 'telLinkName',
130
+ 'Include accessibility in the name of a telephone link on the accessibility page.'
131
+ ]
132
+ );
133
+ }
134
+ }
135
+ }
136
+ }
137
+ query.suggestions = suggestions
138
+ .map(pair => `<li><code>${pair[0]}</code>: ${pair[1]}</li>`)
139
+ .join(innerJoiner);
55
140
  };
@@ -4,12 +4,12 @@
4
4
 
5
5
  Computes scores from Testaro script a11yMessage and adds them to a report.
6
6
  Usage examples:
7
- node score a11yMessage 35k1r
8
- node score a11yMessage
7
+ node score spA11yMessage 35k1r
8
+ node score spA11yMessage
9
9
 
10
10
  This proc computes a score that is intended to represent how accessibly a web page offers
11
11
  a user an opportunity to report an accessibility issue about that page. Scores can range
12
- from perfect 0 to 16.
12
+ from perfect 0 to 23.
13
13
  */
14
14
 
15
15
  // CONSTANTS
@@ -20,17 +20,20 @@ const scoreProcID = 'a11ymessage';
20
20
  // FUNCTIONS
21
21
 
22
22
  // Scores the contact links of a type.
23
- const contactScorer = (result, score, type) => {
23
+ const contactScorer = (result, score, linkProp, linkNameProp) => {
24
+ score[linkProp] = 2;
25
+ // If any of the links is named for accessibility:
24
26
  const links = result.items;
25
27
  if (links.some(
26
28
  link => link.textContent.toLowerCase().includes('accessibility')
27
29
  )) {
28
- score[type] -= 3;
30
+ score[linkNameProp] = 2;
29
31
  }
32
+ // Otherwise, if the link context refers to accessibility:
30
33
  else if (links.some(
31
34
  link => link.parentTextContent.toLowerCase().includes('accessibility')
32
35
  )) {
33
- score[type] -= 2;
36
+ score[linkNameProp] = 1;
34
37
  }
35
38
  };
36
39
  // Scores a report.
@@ -38,67 +41,80 @@ exports.scorer = async report => {
38
41
  const {acts} = report;
39
42
  report.scoreProcID = scoreProcID;
40
43
  report.score = {
41
- page: 3,
42
- a11yLink: 4,
43
- title: 3,
44
- heading: 3,
45
- mailLink: 3,
46
- telLink: 3
44
+ pageLoad: 0,
45
+ pageFast: 0,
46
+ a11yLink: 0,
47
+ a11yLinkWork: 0,
48
+ a11yLinkFast: 0,
49
+ a11yPageTitle: 0,
50
+ a11yTitleGood: 0,
51
+ a11yPageH1: 0,
52
+ a11yH1Good: 0,
53
+ mailLink: 0,
54
+ mailLinkName: 0,
55
+ telLink: 0,
56
+ telLinkName: 0
47
57
  };
48
58
  const {score} = report;
49
59
  if (Array.isArray(acts)) {
50
- // Act 1: page loads.
60
+ // Act 1: If the page loaded:
51
61
  if (acts[1].result.startsWith('http')) {
52
- score.page -= 2;
53
- if (acts[1].endTime - acts[1].startTime < 2500) {
54
- score.page -= 1;
62
+ score.pageLoad = 2;
63
+ // If it loaded moderately fast:
64
+ const loadTime = acts[1].endTime - acts[1].startTime;
65
+ if (loadTime < 6000) {
66
+ score.pageFast = 1;
55
67
  }
56
- // Act 2: accessibility link exists and loads promptly.
68
+ // If it loaded fast:
69
+ if (loadTime < 4000) {
70
+ score.pageFast = 2;
71
+ }
72
+ // Act 2: If the page has an accessibility link:
57
73
  const {result} = acts[2];
58
- // If a link with text content including accessibility was found:
59
- if (result.found) {
60
- score.a11yLink -= 2;
61
- // If it was clickable and the resulting load finished:
74
+ if (result.found) {
75
+ score.a11yLink = 1;
76
+ // If it works:
62
77
  if (result.success) {
63
- score.a11yLink -= 1;
64
- // If the navigation and load took less than 1.5 seconds:
65
- if (acts[2].endTime - acts[2].startTime < 1500) {
66
- score.a11yLink -= 1;
78
+ score.a11yLinkWork = 2;
79
+ // If the resulting page loads fast:
80
+ const loadTime = acts[2].endTime - acts[2].startTime;
81
+ if (loadTime < 5000) {
82
+ score.a11yLinkFast = 1;
83
+ }
84
+ if (loadTime < 3000) {
85
+ score.a11yLinkFast = 2;
67
86
  }
68
- // Act 3: next page has an accessibility title.
87
+ // Act 3: If the resulting page has a title:
69
88
  let {result} = acts[3];
70
89
  if (result && result.success) {
71
- score.title -= 1;
90
+ score.a11yPageTitle = 1;
91
+ // If it is an accessibility title:
72
92
  if (result.title.toLowerCase().includes('accessibility')) {
73
- score.title -= 2;
93
+ score.a11yTitleGood = 2;
74
94
  }
75
95
  }
76
- // Act 4: page has 1 h1 heading, and it is about accessibility.
96
+ // Act 4: If the resulting page has a top heading:
77
97
  result = acts[4].result;
78
98
  if (result && result.total === 1) {
79
- score.heading -= 1;
99
+ score.a11yPageH1 = 1;
100
+ // If it is an accessibility heading:
80
101
  if (result.items[0].textContent.toLowerCase().includes('accessibility')) {
81
- score.heading -= 2;
102
+ score.a11yH1Good = 2;
82
103
  }
83
104
  }
84
- // Act 5: page has an accessibility email link.
105
+ // Act 5: If the resulting page has an accessibility email link:
85
106
  result = acts[5].result;
86
107
  if (result.total) {
87
- contactScorer(result, score, 'mailLink');
108
+ contactScorer(result, score, 'mailLink', 'mailLinkName');
88
109
  }
89
- // Act 6: page has accessibility telephone link.
110
+ // Act 6: If the resulting page has accessibility telephone link:
90
111
  result = acts[6].result;
91
112
  if (result.total) {
92
- contactScorer(result, score, 'telLink');
113
+ contactScorer(result, score, 'telLink', 'telLinkName');
93
114
  }
94
115
  }
95
116
  }
96
117
  }
97
118
  }
98
- score.total = score.page
99
- + score.a11yLink
100
- + score.title
101
- + score.heading
102
- + score.mailLink
103
- + score.telLink;
119
+ score.total = Object.values(score).reduce((total, current) => total + current);
104
120
  };
@@ -0,0 +1,160 @@
1
+ /*
2
+ spProductPrice
3
+ Testilo score proc productPrice
4
+
5
+ Computes scores from Testaro script productPrice and adds them to a report.
6
+ Usage examples:
7
+ node score spProductPrice 35k1r
8
+ node score spProductPrice
9
+
10
+ This proc computes a score that is intended to represent how accessibly a website offers
11
+ a user an opportunity to determine the price at which the website owner offers a product.
12
+ Scores can range from perfect 0 to 16.
13
+ */
14
+
15
+ // CONSTANTS
16
+
17
+ // ID of this proc.
18
+ const scoreProcID = 'productprice';
19
+
20
+ // FUNCTIONS
21
+
22
+ // Returns whether a text contains a U.S. price.
23
+ const hasPrice = (text, isAll) => {
24
+ const matcher = '\\$ ?\\d*(?:,\\d{3})?(?:\\.\\d{2})?(?: *USD)?';
25
+ const trimText = text.trim();
26
+ if (isAll) {
27
+ return new RegExp(`^${matcher}$`).test(trimText);
28
+ }
29
+ else {
30
+ return new RegExp(matcher).test(trimText);
31
+ }
32
+ };
33
+ // Recursively inspects a subtree for semantic marking.
34
+ const findPriceProp = (root, score) => {
35
+ // If no semantically marked price has been found yet:
36
+ if (! score.priceProp) {
37
+ // If the text of the root is exactly a price:
38
+ if (hasPrice(root.text, true)) {
39
+ // If it semantically marks the price as such:
40
+ if (root.attributes.some(
41
+ attribute => attribute.name === 'itemprop' && attribute.value === 'price'
42
+ )) {
43
+ // Add a score and stop looking.
44
+ score.priceProp = 3;
45
+ }
46
+ // Otherwise, i.e. if it does not semantically mark the price:
47
+ else {
48
+ // Inspect the children of the root.
49
+ root.children.forEach(child => {
50
+ findPriceProp(child);
51
+ });
52
+ }
53
+ }
54
+ }
55
+ };
56
+ // Scores a report.
57
+ exports.scorer = async report => {
58
+ const {acts} = report;
59
+ report.scoreProcID = scoreProcID;
60
+ report.score = {
61
+ pageLoad: 0,
62
+ pageFast: 0,
63
+ searchInput: 0,
64
+ searchType: 0,
65
+ searchWork: 0,
66
+ searchFast: 0,
67
+ nameInPage: 0,
68
+ nameInNode: 0,
69
+ nameProp: 0,
70
+ price: 0,
71
+ priceProximity: 0,
72
+ priceProp: 0
73
+ };
74
+ const {score} = report;
75
+ if (Array.isArray(acts)) {
76
+ // Act 1: If the page loaded:
77
+ if (acts[1].result.startsWith('http')) {
78
+ score.pageLoad = 2;
79
+ // Score how fast it loaded.
80
+ const loadTime = acts[1].endTime - acts[1].startTime;
81
+ if (loadTime < 4000) {
82
+ score.pageFast = 2;
83
+ }
84
+ else if (loadTime < 6000) {
85
+ score.pageFast = 1;
86
+ }
87
+ // Act 2: If the page has a search input:
88
+ const {result} = acts[2];
89
+ if (result.found) {
90
+ score.searchInput = 1;
91
+ // If it is a search-type input:
92
+ if (result.attributes && result.attributes.type === 'search') {
93
+ score.searchType = 2;
94
+ }
95
+ // If it works:
96
+ if (result.success) {
97
+ score.searchWork = 2;
98
+ // Score how fast it works.
99
+ const loadTime = acts[2].endTime - acts[2].startTime;
100
+ if (loadTime < 3000) {
101
+ score.searchFast = 3;
102
+ }
103
+ else if (loadTime < 5000) {
104
+ score.searchFast = 2;
105
+ }
106
+ else if (loadTime < 7000) {
107
+ score.searchFast = 1;
108
+ }
109
+ // Act 3: If the product is named on the result page:
110
+ let {result} = acts[3];
111
+ if (result && result.found) {
112
+ score.nameInPage = 2;
113
+ // Act 4: If the product is named by any text node:
114
+ result = acts[4].result;
115
+ if (result && result.nodeCount) {
116
+ score.nameInNode = 2;
117
+ // If any such text node semantically marks the name:
118
+ if (result.items.some(item => {
119
+ const parent = item.ancestors[0];
120
+ return parent.attributes.some(
121
+ attribute => attribute.name === 'itemprop' && attribute.value === 'name'
122
+ );
123
+ })) {
124
+ score.nameProp = 3;
125
+ }
126
+ // Act 4: If a price appears in the text content of an ancestor:
127
+ const priceInContext = result.items.some(
128
+ item => item.ancestors.some(
129
+ ancestor => ancestor.text && hasPrice(ancestor.text, false)
130
+ )
131
+ );
132
+ if (priceInContext) {
133
+ score.price = 1;
134
+ // Act 4: Proximity and semantic specification of a price.
135
+ let priceDistance = Infinity;
136
+ // For each text node containing the product name:
137
+ result.items.forEach(item => {
138
+ // Get the distance to the nearest ancestor with a price in its text content.
139
+ const itemPriceDistance = item.ancestors.findIndex(
140
+ ancestor => ancestor.text && hasPrice(ancestor.text, false)
141
+ );
142
+ // If that distance is less than the smallest one found yet:
143
+ if (itemPriceDistance > -1 && itemPriceDistance < priceDistance) {
144
+ // Update the smallest one found.
145
+ priceDistance = itemPriceDistance;
146
+ // Inspect its subtree for a semantically marked price.
147
+ findPriceProp(item, score);
148
+ }
149
+ // Update the price-proximity score.
150
+ score.priceProximity = Math.max(0, 6 - priceDistance);
151
+ });
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ score.total = Object.values(score).reduce((total, current) => total + current);
160
+ };