Skip to content

Commit

Permalink
Use route overrides to mock config and TDS changes in playwright tests (
Browse files Browse the repository at this point in the history
#2324)

* Use route overrides to mock config and TDS changes in playwright tests

* Update other tests using mock config

* Remove old config patching methods

* Removing logging
  • Loading branch information
sammacbeth committed Nov 14, 2023
1 parent 733eefa commit 392b888
Show file tree
Hide file tree
Showing 10 changed files with 80 additions and 136 deletions.
6 changes: 3 additions & 3 deletions integration-test/amp-protection.spec.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { routeFromLocalhost } from './helpers/testPages'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'

const testSite = 'https://privacy-test-pages.site/privacy-protections/amp/'

test.describe('Test AMP link protection', () => {
test('Redirects AMP URLs correctly', async ({ context, backgroundPage, page }) => {
test('Redirects AMP URLs correctly', async ({ context, backgroundPage, page, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'amp-protection.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'amp-protection.json')
await routeFromLocalhost(page)

await page.goto(testSite, { waitUntil: 'networkidle' })
Expand Down
11 changes: 5 additions & 6 deletions integration-test/click-to-load-facebook.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { EventEmitter } from 'node:events'

import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { loadTestConfig, loadTestTds } from './helpers/testConfig'
import { overridePrivacyConfig, overrideTds } from './helpers/testConfig'
import { routeFromLocalhost } from './helpers/testPages'
import { logPageRequests } from './helpers/requests'

Expand Down Expand Up @@ -52,13 +52,12 @@ function summariseFacebookRequests (requests) {
}

test.describe('Test Facebook Click To Load', () => {
test.beforeEach(async ({ context, backgroundPage }) => {
test.beforeEach(async ({ context, backgroundPage, backgroundNetworkContext }) => {
// Overwrite the parts of the configuration needed for our tests.
await overridePrivacyConfig(backgroundNetworkContext, 'click-to-load-facebook.json')
await overrideTds(backgroundNetworkContext, 'click-to-load-tds.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)

// Overwrite the parts of the configuration needed for our tests.
await loadTestConfig(backgroundPage, 'click-to-load-facebook.json')
await loadTestTds(backgroundPage, 'click-to-load-tds.json')
})

test('CTL: Facebook request blocking/redirecting', async ({ page }) => {
Expand Down
11 changes: 5 additions & 6 deletions integration-test/click-to-load-youtube.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { loadTestConfig, loadTestTds } from './helpers/testConfig'
import { overridePrivacyConfig, overrideTds } from './helpers/testConfig'
import { routeFromLocalhost } from './helpers/testPages'
import { logPageRequests } from './helpers/requests'
import { waitForNetworkIdle } from './helpers/pageWait'
Expand Down Expand Up @@ -75,13 +75,12 @@ function overrideHandler (route) {
}

test.describe('Test YouTube Click To Load', () => {
test.beforeEach(async ({ context, backgroundPage }) => {
test.beforeEach(async ({ context, backgroundPage, backgroundNetworkContext }) => {
// Overwrite the parts of the configuration needed for our tests.
await overridePrivacyConfig(backgroundNetworkContext, 'click-to-load-youtube.json')
await overrideTds(backgroundNetworkContext, 'click-to-load-tds.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)

// Overwrite the parts of the configuration needed for our tests.
await loadTestConfig(backgroundPage, 'click-to-load-youtube.json')
await loadTestTds(backgroundPage, 'click-to-load-tds.json')
})

test('CTL: YouTube request blocking/redirecting', async ({ page }) => {
Expand Down
8 changes: 4 additions & 4 deletions integration-test/fingerprint-protection.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect, getHARPath } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'

const expectedFingerprintValues = {
availTop: 0,
Expand Down Expand Up @@ -51,11 +51,11 @@ test.describe('Fingerprint Defense Tests', () => {
})

test.describe('First Party Fingerprint Randomization', () => {
test.beforeEach(async ({ context, backgroundPage }) => {
await backgroundWait.forExtensionLoaded(context)
test.beforeEach(async ({ context, backgroundNetworkContext }) => {
// Override config to remove unprotected sites. This is because fingerprint.js is loaded
// from localhost by the test runner, so would normally be excluded from protections.
await loadTestConfig(backgroundPage, 'fingerprint-protection.json')
await overridePrivacyConfig(backgroundNetworkContext, 'fingerprint-protection.json')
await backgroundWait.forExtensionLoaded(context)
})

async function runTest (testCase, page) {
Expand Down
131 changes: 38 additions & 93 deletions integration-test/helpers/testConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,111 +2,56 @@ import fs from 'fs'
import path from 'path'

/**
* @typedef {import('@playwright/test').Page} Page - Puppeteer Page
* @typedef {import('@playwright/test').Worker} WebWorker - Puppeteer WebWorker
*/

function parsePath (pathString) {
const pathParts = []

// Split path by '.' but take escaping into account.
let currentPart = ''
let escaped = false
for (const char of pathString) {
if (escaped) {
currentPart += char
escaped = false
continue
}

if (char === '\\') {
escaped = true
continue
}

if (char === '.') {
pathParts.push(currentPart)
currentPart = ''
continue
}

currentPart += char
}

// Find the target Object from that path.
const hostObject = pathParts.shift()
if (!hostObject) {
throw new Error(`Could not find host object for path: ${pathString}`)
}
// eslint-disable-next-line no-eval
let currentObject = eval(hostObject)
for (const pathPart of pathParts) {
currentObject = currentObject[pathPart]
}

return [currentObject, currentPart]
}

/**
* Override some part of the extension config temporarily for an integration
* test.
* Note: Avoid overriding same configuration multiple times. Also ensure
* unloadTestConfig() is called after your tests are done.
* @param {Page | WebWorker} bgPage
* The extension's background page.
* Rewrites the config received from the server with the changes specified in testConfigFilename
* @param {import('@playwright/test').Page | import('@playwright/test').BrowserContext} networkContext
* @param {string} testConfigFilename
* The file name of your JSON test configuration, in the
* /integration-test/data/configs/ directory.
* - the keys are a string containing the configuration's "path", for example
* "globalThis.dbg.tds.tds.trackers.duckduckgo\\.com".
* - the values should be your test configuration to set for that path, for
* the above example an Object containing the tracker entry.
* Note:
* - Paths containing '.' can be escaped with a backslash.
* - Make sure to include the 'globalThis.' (or similar global Object) prefix.
* - There's no need to escape whitespace in paths.
*/
export async function loadTestConfig (bgPage, testConfigFilename) {
export async function overridePrivacyConfig (networkContext, testConfigFilename) {
const filePath = path.resolve(
__dirname, '..', 'data', 'configs', testConfigFilename
)
const testConfig = JSON.parse(fs.readFileSync(filePath).toString())

await bgPage.evaluate(({ pageTestConfig, parsePathString }) => {
globalThis.configBackup = globalThis.configBackup || {}
// eslint-disable-next-line no-eval
eval(parsePathString)

for (const pathString of Object.keys(pageTestConfig)) {
const [target, lastPathPart] = parsePath(pathString)
globalThis.configBackup[pathString] = target[lastPathPart]
target[lastPathPart] = pageTestConfig[pathString]
await networkContext.route('https://staticcdn.duckduckgo.com/trackerblocking/config/**/*', async (route) => {
const url = new URL(route.request().url())
const localPath = path.join(__dirname, '..', 'data', 'staticcdn', url.pathname)
const localConfig = JSON.parse(fs.readFileSync(localPath))
for (const pathString of Object.keys(testConfig)) {
const pathParts = pathString.split('.')
if (pathParts[0] !== 'globalThis' || pathParts[1] !== 'dbg' || pathParts[2] !== 'tds' || pathParts[3] !== 'config') {
throw new Error(`unknown config patch path: ${pathString}`)
}
let target = localConfig
const lastPart = pathParts.pop()
for (const p of pathParts.slice(4)) {
target = target[p]
}
target[lastPart] = testConfig[pathString]
}
return globalThis.dbg.tds._internalOnListUpdate('config', globalThis.dbg.tds.config)
}, {
pageTestConfig: testConfig,
parsePathString: parsePath.toString()
route.fulfill({
status: 200,
body: JSON.stringify(localConfig),
headers: {
etag: 'test'
}
})
})
}

/**
* Load a given TDS file in the extension
* @param {Page | WebWorker} bgPage
* @param {string} tdsFilePath path to TDS file to load (from integration-test/data)
* Rewrites the TDS received from the server with the changes specified in tdsFilePath
* @param {import('@playwright/test').Page | import('@playwright/test').BrowserContext} networkContext
* @param {string} tdsFilePath
*/
export async function loadTestTds (bgPage, tdsFilePath) {
await bgPage.evaluate(async tds => {
// Wait until the default list is loaded.
await globalThis.dbg.tds.ready('tds')

// Then load the test list and wait until the update listeners have
// been called.
return await new Promise(resolve => {
globalThis.dbg.tds.onUpdate('tds', resolve)
globalThis.dbg.setListContents({
name: 'tds',
value: tds
})
export async function overrideTds (networkContext, tdsFilePath) {
await networkContext.route('https://staticcdn.duckduckgo.com/trackerblocking/v6/**/*', async (route) => {
const tds = await fs.promises.readFile(path.join(__dirname, '..', 'data', tdsFilePath), 'utf-8')
route.fulfill({
status: 200,
body: tds,
headers: {
etag: 'test'
}
})
}, JSON.parse(await fs.promises.readFile(path.join(__dirname, '..', 'data', tdsFilePath), 'utf-8')))
})
}
6 changes: 3 additions & 3 deletions integration-test/privacy-dashboard.spec.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { routeFromLocalhost } from './helpers/testPages'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'

const testSite = 'https://privacy-test-pages.site/privacy-protections/request-blocking/'

test.describe('Test privacy dashboard', () => {
test('Should load the dashboard with correct link text', async ({ context, backgroundPage, page }) => {
test('Should load the dashboard with correct link text', async ({ context, backgroundPage, page, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'serviceworker-blocking.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'serviceworker-blocking.json')
await routeFromLocalhost(page)

await page.goto(testSite, { waitUntil: 'networkidle' })
Expand Down
17 changes: 9 additions & 8 deletions integration-test/request-blocking.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from './helpers/playwrightHarness'

Check failure on line 1 in integration-test/request-blocking.spec.js

View workflow job for this annotation

GitHub Actions / playwright-tests (playwright-mv3, 3/4)

[chromium] › request-blocking.spec.js:46:9 › Test request blocking › Should block all the test tracking requests

1) [chromium] › request-blocking.spec.js:46:9 › Test request blocking › Should block all the test tracking requests Test timeout of 30000ms exceeded.
import { forAllConfiguration, forExtensionLoaded } from './helpers/backgroundWait'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'
import { TEST_SERVER_ORIGIN } from './helpers/testPages'

const testHost = 'privacy-test-pages.site'
Expand Down Expand Up @@ -43,10 +43,10 @@ async function runRequestBlockingTest (page, url = testSite) {
}

test.describe('Test request blocking', () => {
test('Should block all the test tracking requests', async ({ page, backgroundPage, context }) => {
test('Should block all the test tracking requests', async ({ page, backgroundPage, context, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'serviceworker-blocking.json')
await forExtensionLoaded(context)
await forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'serviceworker-blocking.json')
const [testCount, pageRequests] = await runRequestBlockingTest(page)

// Verify that no logged requests were allowed.
Expand Down Expand Up @@ -97,10 +97,10 @@ test.describe('Test request blocking', () => {
await page.close()
})

test('serviceworkerInitiatedRequests exceptions should disable service worker blocking', async ({ page, backgroundPage, context }) => {
test('serviceworkerInitiatedRequests exceptions should disable service worker blocking', async ({ page, backgroundPage, context, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'serviceworker-blocking.json')
await forExtensionLoaded(context)
await forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'serviceworker-blocking.json')
await backgroundPage.evaluate(async (domain) => {
/* global dbg */
const { data: config } = dbg.getListContents('config')
Expand Down Expand Up @@ -136,7 +136,8 @@ test.describe('Test request blocking', () => {
}
})

test('Blocking should not run on localhost', async ({ page, backgroundPage, context, manifestVersion }) => {
test('Blocking should not run on localhost', async ({ page, backgroundPage, context, manifestVersion, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'serviceworker-blocking.json')
await forExtensionLoaded(context)
await forAllConfiguration(backgroundPage)
// On MV3 config rules are only created some time after the config is loaded. We can query
Expand Down Expand Up @@ -168,10 +169,10 @@ test.describe('Test request blocking', () => {
}
})

test('protection toggle disables blocking', async ({ page, backgroundPage, context, manifestVersion }) => {
test('protection toggle disables blocking', async ({ page, backgroundPage, context, manifestVersion, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'serviceworker-blocking.json')
await forExtensionLoaded(context)
await forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'serviceworker-blocking.json')

// load with protection enabled
await runRequestBlockingTest(page)
Expand Down
14 changes: 7 additions & 7 deletions integration-test/storage-blocking.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { loadTestConfig, loadTestTds } from './helpers/testConfig'
import { overridePrivacyConfig, overrideTds } from './helpers/testConfig'
import { TEST_SERVER_ORIGIN, routeFromLocalhost } from './helpers/testPages'

const testPageDomain = 'privacy-test-pages.site'
Expand All @@ -15,11 +15,11 @@ async function waitForAllResults (page) {
}

test.describe('Storage blocking Tests', () => {
test(`Blocks storage correctly on https://${testPageDomain}/privacy-protections/storage-blocking/`, async ({ page, backgroundPage, context }) => {
test(`Blocks storage correctly on https://${testPageDomain}/privacy-protections/storage-blocking/`, async ({ page, backgroundPage, context, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'storage-blocking.json')
await overrideTds(backgroundNetworkContext, 'mock-tds.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'storage-blocking.json')
await loadTestTds(backgroundPage, 'mock-tds.json')
await routeFromLocalhost(page)
await page.bringToFront()
await page.goto(`https://${testPageDomain}/privacy-protections/storage-blocking/?store`, { waitUntil: 'networkidle' })
Expand Down Expand Up @@ -93,11 +93,11 @@ test.describe('Storage blocking Tests', () => {
expect(results.results.find(({ id }) => id === testName).value).toBeNull()
}

test.beforeEach(async ({ context, backgroundPage, page }) => {
test.beforeEach(async ({ context, backgroundPage, page, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'storage-blocking.json')
await overrideTds(backgroundNetworkContext, 'mock-tds.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'storage-blocking.json')
await loadTestTds(backgroundPage, 'mock-tds.json')
await routeFromLocalhost(page)

// reset allowlists
Expand Down
6 changes: 3 additions & 3 deletions integration-test/test-canvas.spec.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { test, expect } from './helpers/playwrightHarness'
import { forExtensionLoaded } from './helpers/backgroundWait'
import { routeFromLocalhost } from './helpers/testPages'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'

test.describe('Canvas verification', () => {
test.beforeEach(async ({ context, backgroundPage }) => {
test.beforeEach(async ({ context, backgroundPage, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'fingerprint-protection.json')
await forExtensionLoaded(context)
await loadTestConfig(backgroundPage, 'fingerprint-protection.json')
})

test('Canvas drawing should be different per hostname', async ({ page }) => {
Expand Down
6 changes: 3 additions & 3 deletions integration-test/url-parameters.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from './helpers/playwrightHarness'
import backgroundWait from './helpers/backgroundWait'
import { routeFromLocalhost } from './helpers/testPages'
import { loadTestConfig } from './helpers/testConfig'
import { overridePrivacyConfig } from './helpers/testConfig'

const testSite = 'https://privacy-test-pages.site/privacy-protections/query-parameters/'

Expand Down Expand Up @@ -33,10 +33,10 @@ function getUrlParametersRemoved (bgPage) {
}

test.describe('Test URL tracking parameters protection', () => {
test('Strips tracking parameters correctly', async ({ context, backgroundPage, page, manifestVersion }) => {
test('Strips tracking parameters correctly', async ({ context, backgroundPage, page, manifestVersion, backgroundNetworkContext }) => {
await overridePrivacyConfig(backgroundNetworkContext, 'url-parameters.json')
await backgroundWait.forExtensionLoaded(context)
await backgroundWait.forAllConfiguration(backgroundPage)
await loadTestConfig(backgroundPage, 'url-parameters.json')
await routeFromLocalhost(page)

await page.goto(testSite, { waitUntil: 'networkidle' })
Expand Down

0 comments on commit 392b888

Please sign in to comment.