1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
|
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
/* eslint max-len: ["error", 80] */
"use strict";
const { AMTelemetry } = ChromeUtils.import(
"resource://gre/modules/AddonManager.jsm"
);
loadTestSubscript("head_disco.js");
const AMO_TEST_HOST = "rewritten-for-testing.addons.allizom.org";
const amoServer = AddonTestUtils.createHttpServer({ hosts: [AMO_TEST_HOST] });
const DISCO_URL = `http://${AMO_TEST_HOST}/discoapi`;
// Helper function to retrieve test addon ids from the fixture disco api results
// used in this test, this is set in the `setup` test task below.
let getAddonIdFromDiscoResult;
add_setup(async function() {
await SpecialPowers.pushPrefEnv({
set: [
// Enabling the Data Upload pref may upload data.
// Point data reporting services to localhost so the data doesn't escape.
["toolkit.telemetry.server", "https://localhost:1337"],
["telemetry.fog.test.localhost_port", -1],
["extensions.getAddons.discovery.api_url", DISCO_URL],
],
});
let apiText = await readAPIResponseFixture(AMO_TEST_HOST);
// Parse the Disco API fixture and store it for being used in the
// test case to retrieve the recommended addon that match the needs
// of the particular test case.
const apiResultArray = JSON.parse(apiText).results;
ok(apiResultArray.length, `Mock has ${apiResultArray.length} results`);
// Ensure we do have one extension entry that has is_recommendation set
// to true.
let result = apiResultArray.find(r => r.addon.type === "extension");
is(
typeof result.is_recommendation,
"boolean",
"has is_recommendation property"
);
result.is_recommendation = true;
// Define the helper we'll use in the test tasks that follows to pick an
// addonId to test from the api fixtures.
getAddonIdFromDiscoResult = selectParam => {
const { type, is_recommendation } = selectParam;
const addonId = apiResultArray.find(
r => r.addon.type === type && r.is_recommendation === is_recommendation
)?.addon.guid;
ok(
addonId,
`Got a recommended addon id for ${JSON.stringify(
selectParam
)}: ${addonId}`
);
return addonId;
};
// Make sure that we have marked at least one extension as taar recommended.
getAddonIdFromDiscoResult({ type: "extension", is_recommendation: true });
// Stringify back the mock disco api result and register the HTTP server
// path handler.
apiText = JSON.stringify({ results: apiResultArray });
amoServer.registerPathHandler("/discoapi", (request, response) => {
response.write(apiText);
});
});
async function switchToView(win, viewId) {
win.gViewController.loadView(viewId);
await wait_for_view_load(win);
}
async function run_view_telemetry_test(taarEnabled) {
Services.telemetry.clearEvents();
let win = await loadInitialView("discover");
await switchToView(win, "addons://list/theme");
await switchToView(win, "addons://list/plugin");
await switchToView(win, "addons://list/extension");
await closeView(win);
const taar_enabled = AMTelemetry.convertToString(taarEnabled);
assertAboutAddonsTelemetryEvents(
[
["addonsManager", "view", "aboutAddons", "discover", { taar_enabled }],
["addonsManager", "view", "aboutAddons", "list", { type: "theme" }],
["addonsManager", "view", "aboutAddons", "list", { type: "plugin" }],
["addonsManager", "view", "aboutAddons", "list", { taar_enabled }],
],
{ category: "addonsManager", methods: ["view"] }
);
}
async function run_install_recommended_telemetry_test(taarRecommended) {
const extensionId = getAddonIdFromDiscoResult({
type: "extension",
// Pick an extension that match the taarBased expectation for the test
// (otherwise we would need to change the fixture to cover the case where
// the user did disable the taar-based recommendations).
is_recommendation: taarRecommended,
});
const themeId = getAddonIdFromDiscoResult({
type: "statictheme",
is_recommendation: false,
});
Services.telemetry.clearEvents();
let win = await loadInitialView("discover");
await promiseDiscopaneUpdate(win);
// Test extension install.
info("Install recommended extension");
let installExtensionPromise = promiseAddonInstall(
amoServer,
{
manifest: {
name: "A fake recommended extension",
description: "Test disco taar telemetry",
browser_specific_settings: { gecko: { id: extensionId } },
permissions: ["<all_urls>"],
},
},
{ source: "disco", taarRecommended }
);
await testCardInstall(getCardByAddonId(win, extensionId));
await installExtensionPromise;
// Test theme install.
info("Install recommended theme");
let installThemePromise = promiseAddonInstall(
amoServer,
{
manifest: {
name: "A fake recommended theme",
description: "Test disco taar telemetry",
browser_specific_settings: { gecko: { id: themeId } },
theme: {
colors: {
tab_selected: "red",
},
},
},
},
{ source: "disco", taarRecommended: false }
);
let promiseThemeChange = promiseObserved("lightweight-theme-styling-update");
await testCardInstall(getCardByAddonId(win, themeId));
await installThemePromise;
await promiseThemeChange;
await switchToView(win, "addons://list/extension");
await closeView(win);
info("Uninstall recommended addon");
const extAddon = await AddonManager.getAddonByID(extensionId);
await extAddon.uninstall();
info("Uninstall recommended theme");
promiseThemeChange = promiseObserved("lightweight-theme-styling-update");
const themeAddon = await AddonManager.getAddonByID(themeId);
await themeAddon.uninstall();
await promiseThemeChange;
const taar_based = AMTelemetry.convertToString(taarRecommended);
assertAboutAddonsTelemetryEvents(
[
[
"addonsManager",
"action",
"aboutAddons",
null,
{
action: "installFromRecommendation",
view: "discover",
taar_based,
addonId: extensionId,
type: "extension",
},
],
["addonsManager", "install_stats", "extension", /.*/, { taar_based }],
[
"addonsManager",
"action",
"aboutAddons",
null,
{
action: "installFromRecommendation",
view: "discover",
// taar is expected to be always marked as not active for themes.
taar_based: "0",
addonId: themeId,
type: "theme",
},
],
["addonsManager", "install_stats", "theme", /.*/, { taar_based: "0" }],
],
{
methods: ["action", "install_stats"],
objects: ["theme", "extension", "aboutAddons"],
}
);
}
add_task(async function test_taar_enabled_telemetry() {
await SpecialPowers.pushPrefEnv({
set: [
// Make sure taar-based recommendation are enabled.
["browser.discovery.enabled", true],
["datareporting.healthreport.uploadEnabled", true],
["extensions.htmlaboutaddons.recommendations.enabled", true],
],
});
const expectTaarEnabled = true;
await run_view_telemetry_test(expectTaarEnabled);
await run_install_recommended_telemetry_test(expectTaarEnabled);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_taar_disabled_telemetry() {
await SpecialPowers.pushPrefEnv({
set: [
// Make sure taar-based recommendations are disabled.
["browser.discovery.enabled", false],
["datareporting.healthreport.uploadEnabled", true],
["extensions.htmlaboutaddons.recommendations.enabled", true],
],
});
const expectTaarDisabled = false;
await run_view_telemetry_test(expectTaarDisabled);
await run_install_recommended_telemetry_test(expectTaarDisabled);
await SpecialPowers.popPrefEnv();
});
// Install an add-on by clicking on the card.
// The promise resolves once the card has been updated.
async function testCardInstall(card) {
Assert.deepEqual(
getVisibleActions(card).map(getActionName),
["install-addon"],
"Should have an Install button before install"
);
let installButton =
card.querySelector("[data-l10n-id='install-extension-button']") ||
card.querySelector("[data-l10n-id='install-theme-button']");
let updatePromise = promiseEvent(card, "disco-card-updated");
installButton.click();
await updatePromise;
Assert.deepEqual(
getVisibleActions(card).map(getActionName),
["manage-addon"],
"Should have a Manage button after install"
);
}
function getCardByAddonId(win, addonId) {
for (let card of win.document.querySelectorAll("recommended-addon-card")) {
if (card.addonId === addonId) {
return card;
}
}
return null;
}
// Retrieve the list of visible action elements inside a document or container.
function getVisibleActions(documentOrElement) {
return Array.from(documentOrElement.querySelectorAll("[action]")).filter(
elem =>
elem.getAttribute("action") !== "page-options" &&
elem.offsetWidth &&
elem.offsetHeight
);
}
function getActionName(actionElement) {
return actionElement.getAttribute("action");
}
|