Skip to content

Commit

Permalink
Toggle Report for Windows and Android (#251)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgurgel authored Nov 29, 2024
1 parent df7d26b commit e1ec133
Show file tree
Hide file tree
Showing 72 changed files with 1,094 additions and 292 deletions.
28 changes: 28 additions & 0 deletions guides/toggle-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ This is called immediately to retrieve the data needed to render the list.
- WebKit: {@link "macOS integration".privacyDashboardGetToggleReportOptions}
- Other platforms will be added

See also: [Data disclosure item ids and their meanings](#appendix-data-disclosure-item-ids-and-their-meanings)

## Step 4: Implement new handlers

The following are all sent in response to user interactions
Expand All @@ -42,3 +44,29 @@ The following are all sent in response to user interactions
### 👆Tapping anywhere on the success screen (macos only)

- Webkit: {@link "macOS integration".privacyDashboardClose}

## Appendix: Data disclosure item ids and their meanings

| ID | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------ |
| appVersion | App version number |
| atb | Anonymous experiment group for feature testing |
| description | Your selected category and optional comments |
| device | Device make, model, and manufacturer |
| didOpenReportInfo | Whether or not you opted to show this report info |
| errorDescriptions | Browser-reported errors |
| extensionVersion | Extension version number |
| features | List of which browser features were active |
| httpErrorCodes | Website response status (HTTP) codes |
| jsPerformance | How quickly parts of the page loaded |
| lastSentDay | Date of last report sent for this site |
| listVersions | Information about which versions of our protections were active |
| locale | Primary language and country of your device |
| openerContext | How you got to this page, either: "SERP" (DuckDuckGo search), "Navigation" (link/URL), or "External" (other means) |
| os | Operating system version number |
| reportFlow | Which reporting form you used ("menu", "dashboard", etc.) |
| requests | Hostnames of trackers blocked, surrogate requests, ignored requests, and requests not in tracker blocking list |
| siteUrl | Page URL (without identifiable info) |
| toggleReportCounter | Number of times protections were toggled off |
| userRefreshCount | Number of refreshes since page load |
| wvVersion | Web browser engine version number |
15 changes: 13 additions & 2 deletions integration-tests/DashboardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,15 +248,17 @@ export class DashboardPage {
* @param {import("@playwright/test").Page} page
* @param {object} [opts]
* @param {import('../schema/__generated__/schema.types').EventOrigin['screen']} [opts.screen]
* @param {'menu' | 'dashboard'} [opts.opener]
* @return {Promise<DashboardPage>}
*/
static async windows(page, opts = {}) {
/** @type {import('../schema/__generated__/schema.types').EventOrigin['screen']} */
const screen = opts?.screen || 'primaryScreen';
const opener = opts?.opener || 'dashboard';
const dash = new DashboardPage(page, { name: 'windows' });
await dash.withMarker();
await dash.mocks.install();
await dash.loadPage({ screen });
await dash.loadPage({ screen, opener });
await page.waitForFunction(() => typeof window.__playwright !== 'undefined');
return dash;
}
Expand All @@ -266,7 +268,7 @@ export class DashboardPage {
* @param {import("../shared/js/ui/views/tests/generate-data.mjs").MockData} initial
* @returns {Promise<DashboardPage>}
*/
static async browser(page, initial = testDataStates.empty) {
static async browser(page, initial = testDataStates.empty, opts = {}) {
const dash = new DashboardPage(page, { name: 'browser' });
await dash.withMarker();

Expand Down Expand Up @@ -672,6 +674,15 @@ export class DashboardPage {
await this.mocks.calledForClose({ screen: 'toggleReport' });
}

/**
* @param {import('../schema/__generated__/schema.types').EventOrigin['screen']} screen
*/
async backButtonRejectsToggleReport(screen = 'toggleReport') {
const selector = this.parent(screen);
await this.page.locator(selector).getByLabel('Back', { exact: true }).click();
await this.mocks.calledForRejectToggleReport();
}

async rejectToggleReport() {
const { page } = this;
await page.getByRole('button', { name: `Don't send` }).click();
Expand Down
196 changes: 167 additions & 29 deletions integration-tests/Mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ export class Mocks {
}

async calledForSendToggleReport() {
if (!['browser', 'ios', 'macos'].includes(this.platform.name)) return;
if (this.platform.name === 'android') {
const calls = await this.outgoing({ names: ['sendToggleReport'] });
expect(calls).toMatchObject([['sendToggleReport', undefined]]);
return;
}
if (this.platform.name === 'browser') {
const calls = await this.outgoing({ names: ['sendToggleReport'] });
expect(calls).toMatchObject([
Expand All @@ -91,16 +95,44 @@ export class Mocks {
},
],
]);
} else {
return;
}

if (this.platform.name === 'windows') {
const calls = await this.outgoing({
names: ['SendToggleBreakageReport'],
});
expect(calls).toMatchObject([
[
'SendToggleBreakageReport',
{
Feature: 'PrivacyDashboard',
Name: 'SendToggleBreakageReport',
Data: {},
},
],
]);
return;
}

if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const out = await this.outgoing({
names: ['privacyDashboardSendToggleReport'],
});
expect(out).toMatchObject([['privacyDashboardSendToggleReport', {}]]);
return;
}

throw new Error('unreachable. mockCalledForSendToggleReport must be handled');
}

async calledForRejectToggleReport() {
if (!['browser', 'ios', 'macos'].includes(this.platform.name)) return;
if (this.platform.name === 'android') {
const calls = await this.outgoing({ names: ['rejectToggleReport'] });
expect(calls).toMatchObject([['rejectToggleReport', undefined]]);
return;
}

if (this.platform.name === 'browser') {
const calls = await this.outgoing({ names: ['rejectToggleReport'] });
expect(calls).toMatchObject([
Expand All @@ -111,20 +143,84 @@ export class Mocks {
},
],
]);
} else {
// ios/macos
return;
}

if (this.platform.name === 'windows') {
const calls = await this.outgoing({
names: ['RejectToggleBreakageReport'],
});
expect(calls).toMatchObject([
[
'RejectToggleBreakageReport',
{
Feature: 'PrivacyDashboard',
Name: 'RejectToggleBreakageReport',
Data: {},
},
],
]);
return;
}

if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const out = await this.outgoing({
names: ['privacyDashboardRejectToggleReport'],
});
expect(out).toMatchObject([['privacyDashboardRejectToggleReport', {}]]);

return;
}

throw new Error('unreachable. mockCalledForRejectToggleReport must be handled');
}

async calledForSeeWhatsSent() {
const out = await this.outgoing({
names: ['privacyDashboardSeeWhatIsSent'],
});
expect(out).toMatchObject([['privacyDashboardSeeWhatIsSent', {}]]);
if (this.platform.name === 'android') {
const calls = await this.outgoing({ names: ['seeWhatIsSent'] });
expect(calls).toMatchObject([['seeWhatIsSent', undefined]]);
return;
}

if (this.platform.name === 'browser') {
const calls = await this.outgoing({ names: ['seeWhatIsSent'] });
expect(calls).toMatchObject([
[
'seeWhatIsSent',
{
messageType: 'seeWhatIsSent',
},
],
]);
return;
}

if (this.platform.name === 'windows') {
const calls = await this.outgoing({
names: ['SeeWhatIsSent'],
});
expect(calls).toMatchObject([
[
'SeeWhatIsSent',
{
Feature: 'PrivacyDashboard',
Name: 'SeeWhatIsSent',
Data: {},
},
],
]);
return;
}

if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const out = await this.outgoing({
names: ['privacyDashboardSeeWhatIsSent'],
});
expect(out).toMatchObject([['privacyDashboardSeeWhatIsSent', {}]]);
return;
}

throw new Error('unreachable. mockCalledForSeeWhatsSent must be handled');
}

/**
Expand Down Expand Up @@ -167,39 +263,41 @@ export class Mocks {
}
}
async calledForNativeFeedback() {
const calls = await this.outgoing({
names: ['privacyDashboardShowNativeFeedback'],
});
expect(calls).toMatchObject([['privacyDashboardShowNativeFeedback', {}]]);
}

async calledForSubmitBreakageForm({ category = '', description = '' }) {
if (this.platform.name === 'windows') {
const calls = await this.outgoing({
names: ['SubmitBrokenSiteReport'],
names: ['ShowNativeFeedback'],
});
expect(calls).toMatchObject([
[
'SubmitBrokenSiteReport',
'ShowNativeFeedback',
{
Feature: 'PrivacyDashboard',
Name: 'SubmitBrokenSiteReport',
Data: {
category,
description,
},
Name: 'ShowNativeFeedback',
Data: {},
},
],
]);
return;
}

if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const out = await this.outgoing({
names: ['privacyDashboardSubmitBrokenSiteReport'],
const calls = await this.outgoing({
names: ['privacyDashboardShowNativeFeedback'],
});
expect(out).toMatchObject([['privacyDashboardSubmitBrokenSiteReport', { category, description }]]);
expect(calls).toMatchObject([['privacyDashboardShowNativeFeedback', {}]]);
return;
}

throw new Error('unreachable. mockCalledForNativeFeedback must be handled');
}

async calledForSubmitBreakageForm({ category = '', description = '' }) {
if (this.platform.name === 'android') {
const out = await this.outgoing({ names: ['submitBrokenSiteReport'] });
expect(out).toMatchObject([['submitBrokenSiteReport', JSON.stringify({ category, description })]]);
return;
}

if (this.platform.name === 'browser') {
const out = await this.outgoing({ names: ['submitBrokenSiteReport'] });
expect(out).toMatchObject([
Expand All @@ -216,11 +314,35 @@ export class Mocks {
]);
return;
}
if (this.platform.name === 'android') {
const out = await this.outgoing({ names: ['submitBrokenSiteReport'] });
expect(out).toMatchObject([['submitBrokenSiteReport', JSON.stringify({ category, description })]]);

if (this.platform.name === 'windows') {
const calls = await this.outgoing({
names: ['SubmitBrokenSiteReport'],
});
expect(calls).toMatchObject([
[
'SubmitBrokenSiteReport',
{
Feature: 'PrivacyDashboard',
Name: 'SubmitBrokenSiteReport',
Data: {
category,
description,
},
},
],
]);
return;
}

if (this.platform.name === 'macos' || this.platform.name === 'ios') {
const out = await this.outgoing({
names: ['privacyDashboardSubmitBrokenSiteReport'],
});
expect(out).toMatchObject([['privacyDashboardSubmitBrokenSiteReport', { category, description }]]);
return;
}

throw new Error('unreachable. mockCalledForSubmitBreakageForm must be handled');
}

Expand Down Expand Up @@ -298,6 +420,22 @@ export class Mocks {
expect(calls).toMatchObject([['close', undefined]]);
return;
}
if (this.platform.name === 'windows') {
const calls = await this.outgoing({ names: ['CloseCommand'] });
expect(calls).toMatchObject([
[
'CloseCommand',
{
Data: {
eventOrigin,
},
Feature: 'PrivacyDashboard',
Name: 'CloseCommand',
},
],
]);
return;
}
if (this.platform.name === 'ios' || this.platform.name === 'macos') {
const calls = await this.outgoing({ names: ['privacyDashboardClose'] });
expect(calls).toMatchObject([
Expand Down
35 changes: 35 additions & 0 deletions integration-tests/android.spec-int.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,41 @@ test.describe('Breakage form', () => {
});
});

test.describe('opens toggle report', () => {
test('sends toggle report', { tag: '@screenshots' }, async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.android(page, { screen: 'toggleReport', opener: 'menu' });
await dash.addState([testDataStates.google]);
await dash.toggleReportIsVisible();
await dash.screenshot('screen-toggle-report.png');
await dash.showsOnlyBackButton('toggleReport');
await dash.sendToggleReport();
});
test('back button rejects toggle report', async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.android(page, { screen: 'toggleReport', opener: 'menu' });
await dash.addState([testDataStates.google]);
await dash.toggleReportIsVisible();
await dash.backButtonRejectsToggleReport();
});
test('rejects toggle report', async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.android(page, { screen: 'toggleReport', opener: 'dashboard' });
await dash.addState([testDataStates.google]);
await dash.toggleReportIsVisible();
await dash.rejectToggleReport();
});
test('shows information once', { tag: '@screenshots' }, async ({ page }) => {
/** @type {DashboardPage} */
const dash = await DashboardPage.android(page, { screen: 'toggleReport', opener: 'dashboard' });
await dash.addState([testDataStates.google]);
await dash.toggleReportIsVisible();
await dash.showsInformation();
await dash.cannotHideInformation();
await dash.screenshot('screen-toggle-report-show.png');
});
});

test.describe('Close', () => {
test('pressing close should call native API', async ({ page }) => {
const dash = await DashboardPage.android(page);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e1ec133

Please sign in to comment.