├── .github └── workflows │ ├── linting.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _scripts ├── helpers │ └── tracker-radar-checks.js ├── linting.js ├── package-lock.json ├── package.json └── schemas │ └── tests.json ├── _template-new-feature ├── README.md ├── config_reference.json └── tests.json ├── amp-protections ├── README.MD ├── config_reference.json └── tests.json ├── block-third-party-tracking-cookies ├── README.md ├── config_reference.json ├── tests.json └── tracker_radar_reference.json ├── broken-site-reporting ├── README.md ├── multiple_report_tests.json └── tests.json ├── click-to-load ├── README.md ├── config_reference.json ├── surrogates_reference.txt ├── tds_reference.json └── tests.json ├── expire-first-party-js-cookies ├── README.md ├── config_reference.json ├── tests.json └── tracker_radar_reference.json ├── fingerprinting-protections ├── README.md ├── config_reference.json ├── init.js └── tests.json ├── global-privacy-control ├── README.md ├── config_reference.json └── tests.json ├── https-upgrades ├── README.md ├── config_reference.json ├── https_allowlist_reference.json ├── https_bloomfilter_reference.bin ├── https_bloomfilter_reference.json ├── https_bloomfilter_spec_reference.json ├── https_dont_upgrade_hostnames.txt ├── https_negative_allowlist_reference.json ├── https_negative_bloomfilter_reference.json ├── https_upgrade_hostnames.txt └── tests.json ├── malicious-site-protection ├── README.md ├── block_tests.json ├── canonicalization_tests.json ├── reference_malware_filterSet.json ├── reference_malware_hashPrefixes.json ├── reference_phishing_filterSet.json └── reference_phishing_hashPrefixes.json ├── package.json ├── privacy-configuration ├── README.MD ├── config1_reference.json ├── config2_reference.json ├── config3_reference.json └── tests.json ├── referrer-trimming ├── README.md ├── config_reference.json ├── tests.json └── tracker_radar_reference.json ├── storage-clearing ├── README.md └── tests.json ├── suggestions ├── README.md ├── bookmark-and-history-nav-link-dont-dedupe-mobile.json ├── bookmark-and-history-nav-link-dont-dedupe.json ├── bookmark-nav-link-dont-dedupe-mobile.json ├── bookmark-nav-link-dont-dedupe.json ├── bookmarks-history-open-tabs-basic-mobile.json ├── bookmarks-history-open-tabs-basic.json ├── comprehensive-mixed-sources-mobile.json ├── comprehensive-mixed-sources.json ├── edge-cases-diacritic-case-mobile.json ├── edge-cases-diacritic-case.json ├── favorite-duped-open-tab-mobile.json ├── favorite-duped-open-tab.json ├── favorite-nav-link-dont-dedupe-mobile.json ├── favorite-nav-link-dont-dedupe.json ├── history-duped-open-tab-mobile.json ├── history-duped-open-tab.json ├── history-nav-link-dont-dedupe-mobile.json ├── history-nav-link-dont-dedupe.json ├── history-removed-deduped-open-tabs-mobile.json ├── history-removed-deduped-open-tabs.json ├── maximum-suggestions-desktop.json ├── maximum-suggestions-mobile.json ├── multi-window-priority.json ├── prefix-matching-priority-mobile.json ├── prefix-matching-priority.json ├── quality-based-deduplication-desktop.json ├── quality-based-deduplication-mobile.json ├── search-suggestion-test-scenario-schema.json ├── special-pages-bookmarks-desktop.json ├── special-pages-bookmarks-no-tab-desktop.json ├── special-pages-bookmarks-no-tab-mobile.json ├── special-pages-settings-desktop.json ├── special-pages-settings-no-tab-desktop.json ├── special-pages-settings-no-tab-mobile.json ├── suggestions-fire-window-tabs.json ├── suggestions-regular-window-tabs-mobile.json ├── suggestions-regular-window-tabs.json ├── tab-deduplication-desktop.json ├── tab-deduplication-mobile.json ├── top-hits-history-rules-desktop.json ├── top-hits-history-rules-mobile.json ├── top-hits-open-tab-handling-desktop.json └── top-hits-open-tab-handling-mobile.json ├── tracker-radar-tests └── TR-domain-matching │ ├── README.md │ ├── domain_matching_tests.json │ ├── surrogates.txt │ ├── tracker_allowlist_matching_tests.json │ ├── tracker_allowlist_reference.json │ ├── tracker_allowlist_tds_reference.json │ └── tracker_radar_reference.json └── url-parameters ├── README.md ├── config_reference.json └── tests.json /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - '**.md' 8 | pull_request: 9 | paths-ignore: 10 | - '**.md' 11 | 12 | jobs: 13 | linting: 14 | runs-on: ubuntu-22.04 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 16 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: 16.x 21 | - name: Install dependencies 22 | run: npm install 23 | working-directory: ./_scripts 24 | - name: Run tests 25 | run: npm run test 26 | working-directory: ./_scripts 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-22.04 11 | steps: 12 | - name: Set version variable 13 | run: echo "VERSION=`date +'%s'`" >> $GITHUB_ENV 14 | - uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 15 | with: 16 | token: '${{ secrets.GITHUB_TOKEN }}' 17 | name: 'privacy-reference-tests-${{ env.VERSION }}' 18 | tag: ${{ env.VERSION }} 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test Implementations 2 | # If this job is failing, check if there were any updates to 3 | # https://github.com/duckduckgo/apple-browsers/blob/main/.github/workflows/bsk_pr_checksyml 4 | 5 | on: 6 | push: 7 | branches: [ main ] 8 | paths-ignore: 9 | - '**.md' 10 | pull_request: 11 | paths-ignore: 12 | - '**.md' 13 | 14 | jobs: 15 | extension-mv2: 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | path: 'reference-tests' 21 | - uses: actions/checkout@v4 22 | with: 23 | repository: 'duckduckgo/duckduckgo-privacy-extension' 24 | path: 'extension' 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 18 28 | cache: 'npm' 29 | cache-dependency-path: 'extension/package-lock.json' 30 | - run: cd reference-tests && npm link 31 | - name: Setup Extension 32 | working-directory: ./extension 33 | run: | 34 | npm run install-ci 35 | npm link @duckduckgo/privacy-reference-tests 36 | - name: Test Extension (MV2) 37 | working-directory: ./extension 38 | run: | 39 | npm test 40 | 41 | ddg2dnr: 42 | runs-on: ubuntu-22.04 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | path: 'reference-tests' 47 | - uses: actions/checkout@v4 48 | with: 49 | repository: 'duckduckgo/duckduckgo-privacy-extension' 50 | path: 'extension' 51 | - uses: actions/setup-node@v4 52 | with: 53 | node-version: 18 54 | cache: 'npm' 55 | cache-dependency-path: 'extension/package-lock.json' 56 | - run: cd reference-tests && npm link 57 | - name: Setup Extension 58 | working-directory: ./extension 59 | run: | 60 | npm run install-ci 61 | npm link @duckduckgo/privacy-reference-tests 62 | - name: Test ddg2dnr (MV3) 63 | working-directory: ./extension/packages/ddg2dnr 64 | run: npm test -- --grep Reference 65 | 66 | BrowserServicesKit: 67 | name: BSK unit tests 68 | 69 | runs-on: macos-15 70 | timeout-minutes: 30 71 | 72 | steps: 73 | - name: Check out the code 74 | uses: actions/checkout@v4 75 | with: 76 | repository: 'duckduckgo/apple-browsers' 77 | path: 'apple-monorepo' 78 | submodules: recursive 79 | - name: Check out the tests 80 | uses: actions/checkout@v4 81 | with: 82 | path: 'apple-monorepo/SharedPackages/BrowserServicesKit/Tests/BrowserServicesKitTests/Resources/privacy-reference-tests' 83 | 84 | - name: Set cache key hash 85 | working-directory: ./apple-monorepo/SharedPackages/BrowserServicesKit 86 | run: | 87 | has_only_tags=$(jq '[ .pins[].state | has("version") ] | all' Package.resolved) 88 | if [[ "$has_only_tags" == "true" ]]; then 89 | echo "cache_key_hash=${{ hashFiles('apple-monorepo/SharedPackages/BrowserServicesKit/Package.resolved') }}" >> $GITHUB_ENV 90 | else 91 | echo "Package.resolved contains dependencies specified by branch or commit, skipping cache." 92 | fi 93 | 94 | - name: Cache SPM 95 | if: env.cache_key_hash 96 | uses: actions/cache@v4 97 | with: 98 | path: ./apple-monorepo/SharedPackages/BrowserServicesKit/build 99 | key: ${{ runner.os }}-spm-${{ env.cache_key_hash }} 100 | restore-keys: | 101 | ${{ runner.os }}-spm- 102 | 103 | - name: Select Xcode 104 | run: sudo xcode-select -s /Applications/Xcode_$(, entities: Object, domains: Object}} trackerRadarObject 5 | * @returns {Array<{message:string, location: string}>} 6 | */ 7 | module.exports = (trackerRadarObject) => { 8 | const errors = []; 9 | 10 | function error(message, ...location) { 11 | errors.push({message, location: location.join(' ↣ ')}); 12 | } 13 | 14 | Object.keys(trackerRadarObject.trackers).forEach(domain => { 15 | const tracker = trackerRadarObject.trackers[domain]; 16 | 17 | if (tracker.domain !== domain) { 18 | error('"domain" value doesn\'t match key value', 'trackers', domain); 19 | } 20 | 21 | const trackersOwnerName = tracker.owner.name; 22 | 23 | if (!trackerRadarObject.entities[trackersOwnerName]) { 24 | error('owner name missing from "entities" object', 'trackers', domain); 25 | } else { 26 | if (!trackerRadarObject.entities[trackersOwnerName].domains.includes(domain)) { 27 | error(`missing domain: ${domain}`, 'entities', trackersOwnerName); 28 | } 29 | } 30 | 31 | const entityOwners = Object.keys(trackerRadarObject.entities).filter(owner => trackerRadarObject.entities[owner].domains.includes(domain)); 32 | 33 | if (entityOwners.length > 1) { 34 | error(`${domain} has multiple owners`, 'entities', entityOwners[0]); 35 | } 36 | 37 | const domainsOwnerName = trackerRadarObject.domains[domain]; 38 | 39 | if (!domainsOwnerName) { 40 | error('domain missing form "domains" object', 'trackers', domain); 41 | } else { 42 | if (domainsOwnerName !== trackersOwnerName) { 43 | error('owner name doesn\'t match name of the owner in "domains" object', 'trackers', domain); 44 | } 45 | } 46 | }); 47 | 48 | return errors; 49 | } -------------------------------------------------------------------------------- /_scripts/linting.js: -------------------------------------------------------------------------------- 1 | const glob = require('glob'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | const { exit } = require('process'); 5 | const Ajv = require('ajv').default; 6 | const addFormats = require('ajv-formats'); 7 | const ajv = new Ajv(); 8 | addFormats(ajv); 9 | const checkTrackerRadar = require('./helpers/tracker-radar-checks'); 10 | 11 | const FOLDER_FORMAT = /^([a-z]+\-)*[a-z]+$/; 12 | const TEST_FILE_SCHEMA = JSON.parse(fs.readFileSync('./schemas/tests.json')) 13 | const testValidate = ajv.compile(TEST_FILE_SCHEMA); 14 | const SKIP_FOLDERS = ['_scripts']; 15 | const IGNORE_FOLDER_FORMAT = ['_template-new-feature']; 16 | const SKIP_TEST_FILES = ['tracker_allowlist_matching_tests.json']; // doesn't follow the format, skip 17 | 18 | const root = path.resolve(__dirname, '../'); 19 | const dirs = glob.sync("/*/", { root }); 20 | let featuresCount = 0; 21 | let testsCount = 0; 22 | 23 | dirs.forEach(dir => { 24 | const featureFolderName = path.basename(dir); 25 | 26 | if (SKIP_FOLDERS.includes(featureFolderName)) { 27 | return; 28 | } 29 | 30 | if (!IGNORE_FOLDER_FORMAT.includes(featureFolderName) && !FOLDER_FORMAT.test(featureFolderName)) { 31 | console.error(`❌ ${featureFolderName} doesn't follow the folder naming format`); 32 | exit(1); 33 | } 34 | 35 | console.log('Processing feature directory:', featureFolderName); 36 | 37 | featuresCount++; 38 | 39 | const testFiles = featureFolderName === 'suggestions' 40 | ? glob.sync("/**/*.json", { 41 | root: path.resolve(root, featureFolderName), 42 | ignore: "/**/*schema.json" 43 | }) 44 | : glob.sync("/**/?(*_)tests.json", { 45 | root: path.resolve(root, featureFolderName) 46 | }); 47 | 48 | if (testFiles.length === 0) { 49 | console.error(`❌ tests file missing in the feature folder ${featureFolderName}`); 50 | exit(1); 51 | } 52 | 53 | testFiles.forEach(testFile => { 54 | const testBasename = path.basename(testFile); 55 | 56 | if (SKIP_TEST_FILES.includes(testBasename)) { 57 | console.log(` - ⚠️ skipping ${testBasename}`) 58 | return; 59 | } 60 | 61 | console.log(` - validating ${testBasename}`); 62 | 63 | const testFileObject = JSON.parse(fs.readFileSync(testFile)); 64 | 65 | if (testFileObject['$schema']) { 66 | const schemaPath = path.resolve(path.dirname(testFile), testFileObject['$schema']); 67 | try { 68 | const customSchema = JSON.parse(fs.readFileSync(schemaPath)); 69 | const customValidate = ajv.compile(customSchema); 70 | 71 | if (!customValidate(testFileObject)) { 72 | console.error(` ❌ ${testFile} doesn't follow the custom schema format:`); 73 | console.error(customValidate.errors.map(item => ` - ${item.instancePath}: ${item.message}`).join('\n')); 74 | exit(1); 75 | } 76 | } catch (error) { 77 | console.error(` ❌ Failed to load or parse custom schema ${schemaPath}:`, error.message); 78 | exit(1); 79 | } 80 | } else if (!testValidate(testFileObject)) { 81 | console.error(` ❌ ${testFile} doesn't follow the expected format:`); 82 | console.error(testValidate.errors.map(item => ` - ${item.instancePath}: ${item.message}`).join('\n')); 83 | exit(1); 84 | } 85 | 86 | if (featureFolderName === 'suggestions') { 87 | testsCount += 1; // Suggestion tests have 1 test per file 88 | } else { 89 | Object.keys(testFileObject).forEach(set => testsCount += testFileObject[set].tests.length); 90 | } 91 | }); 92 | 93 | const trackerRadarFiles = glob.sync("/**/tracker_radar*.json", { root: path.resolve(root, featureFolderName) });; 94 | 95 | trackerRadarFiles.forEach(testFile => { 96 | const testBasename = path.basename(testFile); 97 | 98 | console.log(` - validating ${testBasename}`); 99 | 100 | const trackerRadarObject = JSON.parse(fs.readFileSync(testFile)); 101 | 102 | const errors = checkTrackerRadar(trackerRadarObject); 103 | 104 | if (errors.length) { 105 | console.error(` ❌ ${testFile} is not a valid Tracker Radar blocklist:`); 106 | console.error(errors.map(item => ` - ${item.location}: ${item.message}`).join('\n')); 107 | exit(1); 108 | } 109 | }) 110 | 111 | }); 112 | 113 | console.log(` 114 | Number of features: ${featuresCount} 115 | Number of tests: ${testsCount} 116 | All good ✅`); 117 | -------------------------------------------------------------------------------- /_scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "privacy-reference-tests-linting", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "node linting.js" 7 | }, 8 | "author": "Duck Duck Go, Inc.", 9 | "license": "Apache License, Version 2.0", 10 | "dependencies": { 11 | "ajv": "^8.7.1", 12 | "ajv-formats": "^2.1.1", 13 | "glob": "^7.2.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /_scripts/schemas/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "patternProperties": { 4 | "^[a-zA-Z]*$": { 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "desc": { 11 | "type": "string" 12 | }, 13 | "tests": { 14 | "type": "array", 15 | "items": { 16 | "type": "object", 17 | "properties": { 18 | "name": { 19 | "type": "string" 20 | }, 21 | "exceptPlatforms": { 22 | "type": "array", 23 | "items": { 24 | "enum": ["ios-browser", "android-browser", "macos-browser", "windows-browser", "web-extension", "safari-extension", "web-extension-mv3"] 25 | } 26 | } 27 | }, 28 | "patternProperties": { 29 | "^[a-zA-Z]*$": {} 30 | }, 31 | "required": [ 32 | "name" 33 | ], 34 | "additionalProperties": false 35 | }, 36 | "minItems": 1 37 | } 38 | }, 39 | "patternProperties": { 40 | "^[a-zA-Z]*$": {} 41 | }, 42 | "required": [ 43 | "name", 44 | "tests" 45 | ], 46 | "additionalProperties": false 47 | } 48 | }, 49 | "minProperties": 1, 50 | "additionalProperties": false 51 | } 52 | -------------------------------------------------------------------------------- /_template-new-feature/README.md: -------------------------------------------------------------------------------- 1 | # Feature Name Tests 2 | 3 | Privacy Feature: 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of …. In particular it focuses on verifying that: 8 | 9 | - … 10 | 11 | ## Structure 12 | 13 | Test suite specific fields: 14 | 15 | - `featureName` - string - name of the privacy feature as defined in the config 16 | - … 17 | - `expectFeatureEnabled` - bool - if feature is expected to be disabled or not 18 | 19 | ## Pseudo-code implementation 20 | 21 | ``` 22 | for $testSet in test.json 23 | loadReferenceConfig('config_reference.json') 24 | 25 | for $test in $testSet 26 | if $test.exceptPlatforms includes 'current-platform' 27 | skip 28 | 29 | $enabled = isFeatureEnabled( 30 | feature=$test.featureName, 31 | ) 32 | 33 | expect($enabled === $test.expectFeatureEnabled) 34 | ``` 35 | 36 | ## Platform exceptions 37 | 38 | - Explanations for any platform exceptions (`exceptPlatforms`) -------------------------------------------------------------------------------- /_template-new-feature/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "stub feature configuration that should be loaded in order to perform tests" 3 | } -------------------------------------------------------------------------------- /_template-new-feature/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "setName": { 3 | "name": "name of the set", 4 | "desc": "description of the set", 5 | "tests": [ 6 | { 7 | "name": "name of the particular test", 8 | "yourCustomField": "input", 9 | "expectSomething": "output", 10 | "exceptPlatforms": [ 11 | "web-extension", 12 | "safari-extension", 13 | "ios-browser", 14 | "android-browser", 15 | "macos-browser", 16 | "windows-browser" 17 | ] 18 | } 19 | ] 20 | }, 21 | "anotherSet": { 22 | "name": "name of the set", 23 | "desc": "description of the set", 24 | "tests": [ 25 | { 26 | "name": "name of the particular test", 27 | "yourCustomField": "input", 28 | "expectSomething": "output", 29 | "exceptPlatforms": [ 30 | "web-extension", 31 | "safari-extension", 32 | "ios-browser", 33 | "android-browser", 34 | "macos-browser", 35 | "windows-browser" 36 | ] 37 | } 38 | ] 39 | } 40 | } -------------------------------------------------------------------------------- /amp-protections/README.MD: -------------------------------------------------------------------------------- 1 | # AMP Protections Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1201460259279674/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies the implementation of AMP protections. In particular it focuses on verifying that: 8 | 9 | - The original URL is able to be extracted from a google.* URL 10 | - A URL is able to be identified as an AMP URL based on keywords in the URL 11 | 12 | ## Structure 13 | 14 | There are multiple sets of tests in `tests.json`. 15 | 16 | Test suite specific fields: 17 | 18 | - ampURL - string - URL that needs to be rewritten 19 | - expectURL - string - The expected URL to be returned (if string is empty native implementation should return nil) 20 | - expectAmpDetected - bool - if ampURL is expected to be a valid AMP URL 21 | 22 | ## Pseudo-code implementation 23 | 24 | ``` 25 | for $testSet in test.json 26 | loadRemoteConfig($testSet.referenceConfig) 27 | 28 | for $test in $testSet 29 | $cleanURL = AMPExtractor.extractCleanURL($ampURL) 30 | 31 | expect($cleanURL === $test.expectURL) 32 | 33 | OR 34 | 35 | $isAmpUrl = AMPExtractor.isAmpUrl($ampUrl) 36 | expect($isAmpUrl === $test.expectAmpDetected) 37 | ``` -------------------------------------------------------------------------------- /amp-protections/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration has only the ampLinks feature", 3 | "features": { 4 | "ampLinks": { 5 | "state": "enabled", 6 | "exceptions": [ 7 | { 8 | "domain": "example2.com", 9 | "reason": "Site breakage" 10 | } 11 | ], 12 | "settings": { 13 | "linkFormats": [ 14 | "^https?:\\/\\/(?:w{3}\\.)?google\\.\\S{2,}\\/amp\\/s\\/(\\S+)$", 15 | "^https?:\\/\\/\\S+ampproject\\.org\\/v\\/s\\/(\\S+)$" 16 | ], 17 | "keywords": [ 18 | "=amp", 19 | "amp=", 20 | "&", 21 | "amp&", 22 | "/amp", 23 | "amp/", 24 | ".amp", 25 | "amp.", 26 | "%amp", 27 | "amp%", 28 | "_amp", 29 | "amp_", 30 | "?amp" 31 | ], 32 | "deepExtractionEnabled": true 33 | } 34 | } 35 | }, 36 | "version": 1635943904459, 37 | "unprotectedTemporary": [] 38 | } 39 | -------------------------------------------------------------------------------- /amp-protections/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "ampFormats": { 3 | "name": "A URL is able to be extracted from a google formatted URL.", 4 | "desc": "Those tests use config_reference.json where the ampLinks feature contains regexes", 5 | "referenceConfig": "config_reference.json", 6 | "tests": [ 7 | { 8 | "name": "A URL is able to be extracted from a google formatted URL.", 9 | "ampURL": "https://www.google.com/amp/s/www.example.com/some/article", 10 | "expectURL": "https://www.example.com/some/article", 11 | "exceptPlatforms": [] 12 | }, 13 | { 14 | "name": "A URL is able to be extracted from a google formatted URL with a different TLD.", 15 | "ampURL": "https://www.google.fr/amp/s/www.example.com/some/article", 16 | "expectURL": "https://www.example.com/some/article", 17 | "exceptPlatforms": [] 18 | }, 19 | { 20 | "name": "A URL is able to be extracted from a google formatted URL with a TLD containing 2 dots.", 21 | "ampURL": "https://www.google.co.uk/amp/s/www.example.com/some/article", 22 | "expectURL": "https://www.example.com/some/article", 23 | "exceptPlatforms": [] 24 | }, 25 | { 26 | "name": "A URL is able to be extracted from a google formatted URL and preserve URL parameters.", 27 | "ampURL": "https://www.google.com/amp/s/www.example.com/some/article?bla=bla&q=test#header", 28 | "expectURL": "https://www.example.com/some/article?bla=bla&q=test#header", 29 | "exceptPlatforms": [] 30 | }, 31 | { 32 | "name": "A URL is able to be extracted from a google ampproject.org formatted URL.", 33 | "ampURL": "https://example.ampproject.org/v/s/www.example.com/some/article", 34 | "expectURL": "https://www.example.com/some/article", 35 | "exceptPlatforms": [] 36 | }, 37 | { 38 | "name": "A URL is not extracted from a google formatted URL because the resulting URL is in the exceptions.", 39 | "ampURL": "https://www.google.com/amp/s/www.example2.com/some/article", 40 | "expectURL": "", 41 | "exceptPlatforms": [] 42 | } 43 | ] 44 | }, 45 | "ampKeywords": { 46 | "name": "A URL is able to be detected as a potential AMP link", 47 | "desc": "Those tests use config_reference.json where the ampLinks feature contains regexes and AMP keywords", 48 | "referenceConfig": "config_reference.json", 49 | "tests": [ 50 | { 51 | "name": "A URL is able to be detected using /amp or amp/ patterns", 52 | "ampURL": "https://www.euronews.com/green/amp/2021/11/05/warm-water-how-climate-change-is-affecting-the-baltic-sea", 53 | "expectAmpDetected": true, 54 | "exceptPlatforms": [] 55 | }, 56 | { 57 | "name": "A URL is able to be detected using .amp patterns", 58 | "ampURL": "https://www.bbc.com/news/world-asia-china-19432800.amp", 59 | "expectAmpDetected": true, 60 | "exceptPlatforms": [] 61 | }, 62 | { 63 | "name": "A URL is able to be detected using amp. patterns", 64 | "ampURL": "https://amp.theguardian.com/environment/2021/nov/05/british-firm-to-unveil-technology-for-zero-carbon-emission-flights-at-cop26", 65 | "expectAmpDetected": true, 66 | "exceptPlatforms": [] 67 | }, 68 | { 69 | "name": "A URL is able to be detected using ?amp patterns", 70 | "ampURL": "https://www.independent.co.uk/news/world/americas/us-politics/fauci-dog-tests-congress-letter-b1944407.html?amp", 71 | "expectAmpDetected": true, 72 | "exceptPlatforms": [] 73 | }, 74 | { 75 | "name": "A URL is not able to be detected using amp patterns", 76 | "ampURL": "https://example.com/some/article", 77 | "expectAmpDetected": false, 78 | "exceptPlatforms": [] 79 | }, 80 | { 81 | "name": "A URL is not able to be detected because the domain is in the exceptions", 82 | "ampURL": "https://example2.com/amp/some/article", 83 | "expectAmpDetected": false, 84 | "exceptPlatforms": [] 85 | } 86 | ] 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /block-third-party-tracking-cookies/README.md: -------------------------------------------------------------------------------- 1 | # Third Party Cookies Bocking Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199093921854081/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of third party tracker cookie blocking. In particular it focuses on verifying that: 8 | 9 | - `cookie` and `set-cookie` headers are being removed from requests to known trackers 10 | - multiple `cookie` headers are handled 11 | - remote config exceptions are taken into account 12 | - `document.cookie` disallows setting / reading cookies in tracking contexts 13 | 14 | ## Structure 15 | 16 | Files in the folder: 17 | - `config_reference.json` - reference remote configuration file 18 | - `tracker_radar_reference.json` - reference blocklist file 19 | 20 | Test suite specific fields: 21 | 22 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 23 | - `frameURL` - string - URL of an iframe in which the feature is operating (optional - if not set assume main frame context) 24 | - `requestURL` - string - URL to a request being made 25 | - `requestHeaders` - array of `{name: string, value:string}` objects - input array of request headers (with `cookie` headers) 26 | - `responseHeaders` - array of `{name: string, value:string}` objects - input array of response headers (with `set-cookie` headers) 27 | - `expectCookieHeadersRemoved` - boolean - if all `cookie` headers are expected to be filtered out for this request 28 | - `expectSetCookieHeadersRemoved` - boolean - if all `set-cookie` headers are expected to be filtered out for this response 29 | - `setDocumentCookie` - string - value that should be assigned to `document.cookie` to test if cookie will be stored 30 | - `expectDocumentCookieSet` - boolean - if cookie set via `document.cookie` is supposed to be stored 31 | 32 | ## Pseudo-code implementation 33 | 34 | ``` 35 | for $testSet in test.json 36 | loadReferenceConfig('config_reference.json') 37 | loadReferenceBlocklist('tracker_radar_reference.json') 38 | 39 | for $test in $testSet 40 | if $test.exceptPlatforms includes 'current-platform' 41 | skip 42 | 43 | if ("expectCookieHeadersRemoved" in $test) { 44 | $outputHeaders = requestBlockTracking3pCookies( 45 | siteURL = $test.siteURL 46 | frameURL = $test.frameURL 47 | headers = $test.requestHeaders 48 | ) 49 | 50 | if ($test.expectCookieHeadersRemoved) { 51 | expect(outputHeaders.countHeaders('cookie') === 0) 52 | } else { 53 | expect(outputHeaders.countHeaders('cookie') > 0) 54 | } 55 | } else if ("expectSetCookieHeadersRemoved" in $test) { 56 | $outputHeaders = responseBlockTracking3pCookies( 57 | siteURL = $test.siteURL 58 | frameURL = $test.frameURL 59 | headers = $test.responseHeaders 60 | ) 61 | 62 | if ($test.expectSetCookieHeadersRemoved) { 63 | expect(outputHeaders.countHeaders('set-cookie') === 0) 64 | } else { 65 | expect(outputHeaders.countHeaders('set-cookie') > 0) 66 | } 67 | } else if ("expectDocumentCookieSet" in $test) { 68 | // testing JS API 69 | $page = createPage( 70 | siteURL = $test.siteURL, 71 | frameURL = $test.frameURL 72 | ) 73 | 74 | $page.eval('document.cookie = ' + $test.setDocumentCookie) 75 | 76 | if (expectDocumentCookieSet) { 77 | expect($page.getCookieString() === $test.setDocumentCookie) 78 | } else { 79 | expect($page.getCookieString() === '') 80 | } 81 | } 82 | ``` 83 | 84 | ## Platform exceptions 85 | 86 | - web extension is missing a CNAME check when determining if domain is a tracker 87 | -------------------------------------------------------------------------------- /block-third-party-tracking-cookies/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration has only the trackingCookies3p feature", 3 | "features": { 4 | "trackingCookies3p": { 5 | "settings": { 6 | "excludedCookieDomains": [ 7 | { 8 | "domain": "excluded-cookie-tracker.com", 9 | "reason": "Site breakage" 10 | } 11 | ] 12 | }, 13 | "exceptions": [ 14 | { 15 | "domain": "exception.local.site", 16 | "reason": "Site breakage" 17 | } 18 | ], 19 | "state": "enabled" 20 | }, 21 | "nonTracking3pCookies": { 22 | "state": "disabled", 23 | "settings": { 24 | "excludedCookieDomains": [ ] 25 | }, 26 | "exceptions": [ ] 27 | }, 28 | "cookie": { 29 | "state": "enabled", 30 | "settings": { 31 | "excludedCookieDomains": [ 32 | { 33 | "domain": "excluded-cookie-tracker.com", 34 | "reason": "Site breakage" 35 | } 36 | ], 37 | "trackerCookie": "enabled", 38 | "nonTrackerCookie": "disabled", 39 | "firstPartyTrackerCookiePolicy": { 40 | "threshold": 86400, 41 | "maxAge": 86400 42 | }, 43 | "firstPartyCookiePolicy": { 44 | "threshold": 604800, 45 | "maxAge": 604800 46 | } 47 | }, 48 | "exceptions": [ 49 | { 50 | "domain": "exception.local.site", 51 | "reason": "Site breakage" 52 | } 53 | ] 54 | } 55 | }, 56 | "version": 1635943904459, 57 | "unprotectedTemporary": [ 58 | { 59 | "domain": "exception.global.site", 60 | "reason": "Site breakage" 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /block-third-party-tracking-cookies/tracker_radar_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackers": { 3 | "tracker.com": { 4 | "domain": "tracker.com", 5 | "owner": { 6 | "name": "Tracker INC", 7 | "displayName": "Tracker INC", 8 | "privacyPolicy": "https://tracker.com/lies", 9 | "url": "https://tracker.com/" 10 | }, 11 | "prevalence": 0.1, 12 | "fingerprinting": 3, 13 | "cookies": 0.1, 14 | "categories": [], 15 | "default": "block", 16 | "rules": [ 17 | { 18 | "rule": "tracker\\.com\\/track", 19 | "action": "ignore" 20 | } 21 | ] 22 | }, 23 | "unblockable-tracker.net": { 24 | "domain": "unblockable-tracker.net", 25 | "owner": { 26 | "name": "Unblockable Tracker INC", 27 | "displayName": "Unblockable Tracker INC", 28 | "privacyPolicy": "https://unblockable-tracker.net/lies", 29 | "url": "https://unblockable-tracker.net/" 30 | }, 31 | "prevalence": 0.1, 32 | "fingerprinting": 3, 33 | "cookies": 0.1, 34 | "categories": [], 35 | "default": "ignore", 36 | "rules": [] 37 | }, 38 | "excluded-cookie-tracker.com": { 39 | "domain": "excluded-cookie-tracker.com", 40 | "owner": { 41 | "name": "Tracker INC", 42 | "displayName": "Tracker INC", 43 | "privacyPolicy": "https://tracker.com/lies", 44 | "url": "https://tracker.com/" 45 | }, 46 | "prevalence": 0.1, 47 | "fingerprinting": 3, 48 | "cookies": 0.1, 49 | "categories": [], 50 | "default": "block", 51 | "rules": [] 52 | } 53 | }, 54 | "entities": { 55 | "Company1": { 56 | "domains": [ 57 | "company1.com", 58 | "company1.co.uk" 59 | ], 60 | "prevalence": 0.1, 61 | "displayName": "Company 1" 62 | }, 63 | "Company2": { 64 | "domains": [ 65 | "company2.com", 66 | "company2.co.uk" 67 | ], 68 | "prevalence": 0.1, 69 | "displayName": "Company 2" 70 | }, 71 | "Tracker INC": { 72 | "domains": [ 73 | "tracker.com", 74 | "tracker.test", 75 | "excluded-cookie-tracker.com" 76 | ], 77 | "prevalence": 0.1, 78 | "displayName": "Tracker INC" 79 | }, 80 | "Unblockable Tracker INC": { 81 | "domains": [ 82 | "unblockable-tracker.net" 83 | ], 84 | "prevalence": 0.1, 85 | "displayName": "Unblockable Tracker INC" 86 | } 87 | }, 88 | "cnames": { 89 | "tracker.cname.com": "bad.tracker.com" 90 | }, 91 | "domains": { 92 | "company1.com": "Company 1", 93 | "company1.co.uk": "Company 1", 94 | "company2.com": "Company 2", 95 | "company2.co.uk": "Company 2", 96 | "tracker.com": "Tracker INC", 97 | "tracker.test": "Tracker INC", 98 | "excluded-cookie-tracker.com": "Tracker INC", 99 | "unblockable-tracker.net": "Unblockable Tracker INC" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /click-to-load/README.md: -------------------------------------------------------------------------------- 1 | # Click To Load Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199651947726592/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies the Click to Load feature: 8 | 9 | - Blocks/redirects requests when Click to Load is enabled for a tab. 10 | - Ignores those requests when: 11 | - Click to Load isn't enabled for a tab. 12 | - The user clicked to load the content. 13 | - The content is first-party. 14 | - The Click to Load rule action isn't supported. 15 | 16 | These tests do not aim to verify the placeholders, or other UI aspects of the feature. 17 | 18 | ## Structure 19 | 20 | Files: 21 | 22 | - `tds_reference.json` - Block list to be used when running the Click to Load tests. 23 | - `config_reference.json` - Configuration to be used when running the Click to Load tests. 24 | - `surrogates_reference.txt` - Surrogate script configuration to be used when running the Click to Load tests. 25 | - `tests.json` - The Click to Load tests. 26 | 27 | Test case fields: 28 | 29 | - `siteUrl` - string - The currently loaded website's URL (as seen in the URL bar). 30 | - `requestUrl` - string - The URL of the request being made. 31 | - `userUnblockedRuleActions` - string[] - Array of Click to Load rule action that the user has unblocked for the page. 32 | - `expectedOutcome` - "block", "redirect" or "ignore" - how request is expected to be handled ('redirect' means that surrogate was loaded instead of the original request). 33 | 34 | ## Pseudo-code implementation 35 | 36 | ``` 37 | loadReferenceConfig('config_reference.json') 38 | loadReferenceBlocklist(tds_reference.json') 39 | 40 | // Find the list of supported Click to Load rule actions. 41 | $supportedRuleActions = [] 42 | if $config.features.clickToLoad.state == enabled: 43 | for $entity, $entity_settings in $config.features.clickToLoad.settings: 44 | if $entity_settings.state == enabled: 45 | for $ruleAction in $entity_settings.ruleActions: 46 | $supportedRuleActions.push($ruleAction) 47 | 48 | for $testSet in test.json 49 | for $test in $testSet 50 | if $test.exceptPlatforms includes 'current-platform' 51 | skip 52 | 53 | // Prevent user unblocked content from being blocked. 54 | $actions = $supportedRuleActions.copy() 55 | for $action in $test.userUnblockedRuleActions: 56 | $actions.remove($action) 57 | 58 | // Prevent first-party blocking. 59 | $entity = findParentEntity($test.siteUrl) 60 | if $config.features.clickToLoad.settings[$entity]: 61 | for $action in $config.features.clickToLoad.settings[$entity].ruleActions: 62 | $actions.remove($action) 63 | 64 | // Prevent blocking Click to Load content if feature is disabled for tab. 65 | if !checkFeatureEnabled($test.siteUrl, "clickToLoad"): 66 | $actions = [] 67 | 68 | // Check the expected matching outcome is correct for the test request. 69 | $result = findMatch($test.siteUrl, $test.requestUrl, $actions) 70 | expect($action).toBe($test.expectedOutcome) 71 | ``` 72 | -------------------------------------------------------------------------------- /click-to-load/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "Test configuration for the Click to Load feature.", 3 | "features": { 4 | "clickToLoad": { 5 | "state": "enabled", 6 | "exceptions": [{ 7 | "domain": "allowed.example", 8 | "reason": "A really good reason" 9 | }], 10 | "settings": { 11 | "Facebook, Inc.": { 12 | "state": "enabled", 13 | "ruleActions": ["block-ctl-fb", "block-ctl-fb2"] 14 | }, 15 | "Second Entity": { 16 | "state": "disabled", 17 | "ruleActions": ["block-ctl-second"] 18 | }, 19 | "Third Entity": { 20 | "state": "enabled", 21 | "ruleActions": ["block-ctl-third"] 22 | } 23 | } 24 | }, 25 | "contentBlocking": { 26 | "state": "enabled" 27 | } 28 | }, 29 | "version": 1676030961458 30 | } 31 | -------------------------------------------------------------------------------- /click-to-load/surrogates_reference.txt: -------------------------------------------------------------------------------- 1 | facebook.net/sdk.js application/javascript 2 | () => (); 3 | -------------------------------------------------------------------------------- /click-to-load/tds_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackers": { 3 | "facebook.com": { 4 | "domain": "facebook.com", 5 | "default": "block", 6 | "owner": { 7 | "name": "Facebook, Inc.", 8 | "displayName": "Facebook" 9 | } 10 | }, 11 | "facebook.net": { 12 | "domain": "facebook.net", 13 | "default": "ignore", 14 | "owner": { 15 | "name": "Facebook, Inc.", 16 | "displayName": "Facebook" 17 | }, 18 | "rules": [ 19 | { 20 | "rule": "facebook\\.net/first-tracker", 21 | "action": "block-ctl-fb" 22 | }, 23 | { 24 | "rule": "facebook\\.net/different-tracker", 25 | "action": "block-ctl-fb2" 26 | }, 27 | { 28 | "rule": "facebook\\.net/unknown-tracker", 29 | "action": "block-ctl-unknown" 30 | }, 31 | { 32 | "rule": "facebook\\.net/script\\.js", 33 | "surrogate": "sdk.js", 34 | "action": "block-ctl-fb" 35 | } 36 | ] 37 | }, 38 | "second.example": { 39 | "domain": "second.example", 40 | "default": "ignore", 41 | "owner": { 42 | "name": "Second Entity", 43 | "displayName": "Second Entity." 44 | }, 45 | "rules": [ 46 | { 47 | "rule": "second\\.example/tracker", 48 | "action": "block-ctl-second" 49 | } 50 | ] 51 | }, 52 | "third.example": { 53 | "domain": "third.example", 54 | "default": "ignore", 55 | "owner": { 56 | "name": "Third Entity", 57 | "displayName": "Third Entity." 58 | }, 59 | "rules": [ 60 | { 61 | "rule": "third\\.example/tracker", 62 | "action": "block-ctl-third" 63 | } 64 | ] 65 | } 66 | }, 67 | "entities": { 68 | "Facebook, Inc.": { 69 | "domains": [ 70 | "facebook.com", 71 | "facebook.net" 72 | ], 73 | "displayName": "Facebook" 74 | }, 75 | "Second Entity": { 76 | "domains": [ 77 | "second.example" 78 | ], 79 | "displayName": "Second Entity" 80 | }, 81 | "Third Entity": { 82 | "domains": [ 83 | "third.example" 84 | ], 85 | "displayName": "Third Entity" 86 | } 87 | 88 | }, 89 | "domains": { 90 | "facebook.com": "Facebook, Inc.", 91 | "facebook.net": "Facebook, Inc.", 92 | "second.example": "Second Entity", 93 | "third.example": "Third Entity" 94 | }, 95 | "cnames": { 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /expire-first-party-js-cookies/README.md: -------------------------------------------------------------------------------- 1 | # Expire First Party JavaScript Cookies 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1200040513378697/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of expiring first party cookies set by scripts. In particular it focuses on verifying that: 8 | 9 | - cookie policy from the configuration file is correctly extracted and applied, 10 | - exceptions from the config are respected, 11 | - reduced expiry time is correctly calculated and applied. 12 | 13 | ## Structure 14 | 15 | Test suite specific fields: 16 | 17 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 18 | - `scriptURL` - string - URL of the script setting the cookie 19 | - `setDocumentCookie` - string - cookie creation string, as assigned to `document.cookie` 20 | - `expectCookieSet` - boolean - if cookie is expected to be created/stored 21 | - `expectExpiryToBe` - number - expected expiry date in seconds from when the cookie was set ("-1" if session cookie is expected) 22 | 23 | ## Pseudo-code implementation 24 | 25 | ``` 26 | for $testSet in test.json 27 | loadReferenceConfig('config_reference.json') 28 | loadReferenceBlocklist('tracker_radar_reference.json') 29 | 30 | for $test in $testSet 31 | if $test.exceptPlatforms includes 'current-platform' 32 | skip 33 | 34 | $page = createPage( 35 | siteURL = $test.siteURL, 36 | ) 37 | 38 | $page.evalAs('document.cookie = ' + $test.setDocumentCookie, $test.scriptURL) 39 | 40 | if ($test.expectCookieSet) { 41 | cookie = $page.getCookies()[0] 42 | 43 | if ($test.expectExpiryToBe === -1) { 44 | expect(cookie.isSessionCookie()).toBe(true) 45 | } else { 46 | expect(cookie.isSessionCookie()).toBe(false) 47 | expect(cookie.expiresInSeconds()).toBe($test.expectExpiryToBe) 48 | } 49 | } else { 50 | expect($page.getCookies().length).toBe(0) 51 | } 52 | ``` 53 | 54 | ## Platform exceptions 55 | 56 | N/A 57 | -------------------------------------------------------------------------------- /expire-first-party-js-cookies/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration includes only the 1p cookie expiry feature.", 3 | "features": { 4 | "trackingCookies1p": { 5 | "settings": { 6 | "firstPartyCookiePolicy": { 7 | "threshold": 604800, 8 | "maxAge": 86400 9 | } 10 | }, 11 | "exceptions": [ 12 | { 13 | "domain": "local-exception.test", 14 | "reason": "cookie should not be modified" 15 | } 16 | ], 17 | "state": "enabled" 18 | }, 19 | "cookie": { 20 | "state": "enabled", 21 | "settings": { 22 | "excludedCookieDomains": [ 23 | { 24 | "domain": "excluded-cookie-domain.com", 25 | "reason": "Site breakage" 26 | } 27 | ], 28 | "trackerCookie": "disabled", 29 | "nonTrackerCookie": "disabled", 30 | "firstPartyCookiePolicy": { 31 | "threshold": 604800, 32 | "maxAge": 86400 33 | } 34 | }, 35 | "exceptions": [ 36 | { 37 | "domain": "local-exception.test", 38 | "reason": "cookie should not be modified" 39 | } 40 | ] 41 | } 42 | }, 43 | "version": 1635943904459, 44 | "unprotectedTemporary": [ 45 | { 46 | "domain": "global-exception.test", 47 | "reason": "cookie should not be modified" 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /expire-first-party-js-cookies/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "expireFirstPartyTrackingCookies": { 3 | "name": "Expire 1p JavaScript cookies", 4 | "desc": "Expiration date should be modified for 1p cookies set by scripts according to the policy from config", 5 | "tests": [ 6 | { 7 | "name": "below treshold - expiry not modified", 8 | "siteURL": "https://example.com/", 9 | "scriptURL": "https://tracker.com/script.js", 10 | "setDocumentCookie": "foo=bar; Max-Age=1000; Domain=example.com; Path=/; Secure", 11 | "expectCookieSet": true, 12 | "expectExpiryToBe": 1000, 13 | "exceptPlatforms": [] 14 | }, 15 | { 16 | "name": "above threshold - expiry modified", 17 | "siteURL": "https://example.com/", 18 | "scriptURL": "https://tracker.com/script.js", 19 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=example.com; Path=/; Secure", 20 | "expectCookieSet": true, 21 | "expectExpiryToBe": 86400, 22 | "exceptPlatforms": [] 23 | }, 24 | { 25 | "name": "same origin, above threshold - expiry modified", 26 | "siteURL": "https://tracker.com/", 27 | "scriptURL": "https://tracker.com/script.js", 28 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=tracker.com; Path=/; Secure", 29 | "expectCookieSet": true, 30 | "expectExpiryToBe": 86400, 31 | "exceptPlatforms": [] 32 | }, 33 | { 34 | "name": "same entity, above threshold - expiry modified", 35 | "siteURL": "https://tracker.test/", 36 | "scriptURL": "https://tracker.com/script.js", 37 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=tracker.test; Path=/; Secure", 38 | "expectCookieSet": true, 39 | "expectExpiryToBe": 86400, 40 | "exceptPlatforms": [] 41 | }, 42 | { 43 | "name": "site safelisted locally - expiry not modified", 44 | "siteURL": "https://local-exception.test/", 45 | "scriptURL": "https://tracker.com/script.js", 46 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=local-exception.test; Path=/; Secure", 47 | "expectCookieSet": true, 48 | "expectExpiryToBe": 900000, 49 | "exceptPlatforms": [] 50 | }, 51 | { 52 | "name": "site safelisted globally - expiry not modified", 53 | "siteURL": "https://global-exception.test/", 54 | "scriptURL": "https://tracker.com/script.js", 55 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=global-exception.test; Path=/; Secure", 56 | "expectCookieSet": true, 57 | "expectExpiryToBe": 900000, 58 | "exceptPlatforms": [] 59 | }, 60 | { 61 | "name": "above treshold - unblockable tracking script (action=ignore) - expiry modified", 62 | "siteURL": "https://example.com/", 63 | "scriptURL": "https://unblockable-tracker.net/script.js", 64 | "setDocumentCookie": "foo=bar; Max-Age=900000; Domain=example.com; Path=/; Secure", 65 | "expectCookieSet": true, 66 | "expectExpiryToBe": 86400, 67 | "exceptPlatforms": [] 68 | }, 69 | { 70 | "name": "above treshold - expiration set to a specific date - expiry modified", 71 | "siteURL": "https://example.com/", 72 | "scriptURL": "https://tracker.com/script.js", 73 | "setDocumentCookie": "foo=bar; expires=Wed, 21 Aug 2030 20:00:00 UTC; Domain=example.com; Path=/; Secure", 74 | "expectCookieSet": true, 75 | "expectExpiryToBe": 86400, 76 | "exceptPlatforms": [] 77 | }, 78 | { 79 | "name": "below treshold - expiration set in the past - cookie not set", 80 | "siteURL": "https://example.com/", 81 | "scriptURL": "https://tracker.com/script.js", 82 | "setDocumentCookie": "foo=bar; expires=Thu, 01-Jan-1970 00:00:01 GMT; Domain=example.com; Path=/; Secure", 83 | "expectCookieSet": false, 84 | "exceptPlatforms": [] 85 | }, 86 | { 87 | "name": "session cookie - expiry not modified", 88 | "siteURL": "https://example.com/", 89 | "scriptURL": "https://tracker.com/script.js", 90 | "setDocumentCookie": "foo=bar", 91 | "expectCookieSet": true, 92 | "expectExpiryToBe": -1, 93 | "exceptPlatforms": [] 94 | } 95 | ] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /expire-first-party-js-cookies/tracker_radar_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackers": { 3 | "tracker.com": { 4 | "domain": "tracker.com", 5 | "owner": { 6 | "name": "Tracker INC", 7 | "displayName": "Tracker INC", 8 | "privacyPolicy": "https://tracker.com/lies", 9 | "url": "https://tracker.com/" 10 | }, 11 | "prevalence": 0.1, 12 | "fingerprinting": 3, 13 | "cookies": 0.1, 14 | "categories": [], 15 | "default": "block", 16 | "rules": [ 17 | { 18 | "rule": "tracker\\.com\\/script.js", 19 | "action": "ignore" 20 | } 21 | ] 22 | }, 23 | "unblockable-tracker.net": { 24 | "domain": "unblockable-tracker.net", 25 | "owner": { 26 | "name": "Unblockable Tracker INC", 27 | "displayName": "Unblockable Tracker INC", 28 | "privacyPolicy": "https://unblockable-tracker.net/lies", 29 | "url": "https://unblockable-tracker.net/" 30 | }, 31 | "prevalence": 0.1, 32 | "fingerprinting": 3, 33 | "cookies": 0.1, 34 | "categories": [], 35 | "default": "ignore", 36 | "rules": [] 37 | }, 38 | "excluded-cookie-tracker.com": { 39 | "domain": "excluded-cookie-tracker.com", 40 | "owner": { 41 | "name": "Tracker INC", 42 | "displayName": "Tracker INC", 43 | "privacyPolicy": "https://tracker.com/lies", 44 | "url": "https://tracker.com/" 45 | }, 46 | "prevalence": 0.1, 47 | "fingerprinting": 3, 48 | "cookies": 0.1, 49 | "categories": [], 50 | "default": "block", 51 | "rules": [] 52 | } 53 | }, 54 | "entities": { 55 | "Company1": { 56 | "domains": [ 57 | "company1.com", 58 | "company1.co.uk" 59 | ], 60 | "prevalence": 0.1, 61 | "displayName": "Company 1" 62 | }, 63 | "Company2": { 64 | "domains": [ 65 | "company2.com", 66 | "company2.co.uk" 67 | ], 68 | "prevalence": 0.1, 69 | "displayName": "Company 2" 70 | }, 71 | "Tracker INC": { 72 | "domains": [ 73 | "tracker.com", 74 | "tracker.test", 75 | "excluded-cookie-tracker.com" 76 | ], 77 | "prevalence": 0.1, 78 | "displayName": "Tracker INC" 79 | }, 80 | "Unblockable Tracker INC": { 81 | "domains": [ 82 | "unblockable-tracker.net" 83 | ], 84 | "prevalence": 0.1, 85 | "displayName": "Unblockable Tracker INC" 86 | } 87 | }, 88 | "cnames": { 89 | "tracker.cname.com": "bad.tracker.com" 90 | }, 91 | "domains": { 92 | "company1.com": "Company 1", 93 | "company1.co.uk": "Company 1", 94 | "company2.com": "Company 2", 95 | "company2.co.uk": "Company 2", 96 | "tracker.com": "Tracker INC", 97 | "tracker.test": "Tracker INC", 98 | "excluded-cookie-tracker.com": "Tracker INC", 99 | "unblockable-tracker.net": "Unblockable Tracker INC" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /fingerprinting-protections/README.md: -------------------------------------------------------------------------------- 1 | # Fingerprinting Protection Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199583771657237/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of fingerprinting protections. In particular it focuses on verifying that: 8 | 9 | - exceptions from configuration file are respected, 10 | - values of properties are modified correctly. 11 | 12 | ## Structure 13 | 14 | Test suite specific files: 15 | 16 | - `init.js` - set of API mocks that should be loaded into the page before protections are initialized. This allows us to normalize default/unprotected values. 17 | 18 | Test suite specific fields: 19 | 20 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 21 | - `property` - string - JavaScript code extracting value of given property/API. Note that in some cases this code is async, please wait for returned promise to resolve. 22 | - `expectPropertyValue` - string - expected value of the property 23 | 24 | ## Pseudo-code implementation 25 | 26 | ``` 27 | $page.injectOnLoad('init.js') 28 | $page.injectOnLoad('C-S-S') 29 | 30 | for $testSet in test.json 31 | loadReferenceConfig('config_reference.json') 32 | 33 | for $test in $testSet.tests 34 | if $test.exceptPlatforms includes 'current-platform' 35 | skip 36 | 37 | $page = createPage( 38 | siteURL = $test.siteURL, 39 | ) 40 | 41 | $value = $page.eval($test.property) 42 | 43 | if ($value instanceof Promise) 44 | $value = await $value 45 | 46 | expect($value.toString()).toBe($test.expectPropertyValue) 47 | ``` 48 | 49 | ## Platform exceptions 50 | 51 | - "ports are ignored when matching rules" disabled for web extensions ([bug report](https://app.asana.com/0/892838074342800/1201806214352982/f)) 52 | - Subdomains of CNAME entries are so far matched for Chrome MV3 ([bug report](https://app.asana.com/0/1200940319964997/1202246325586612/f)) 53 | - Webkit based platforms don't support Battery API, `navigator.keyboard` and `navigator.webkitTemporaryStorage`. 54 | -------------------------------------------------------------------------------- /global-privacy-control/README.md: -------------------------------------------------------------------------------- 1 | # Global Privacy Control Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199115248606508/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of Global Privacy Control signal. In particular it focuses on verifying that: 8 | 9 | - that the GPC header appears on all requests 10 | - that `navigator.globalPrivacyControl` API is available in all frames 11 | - that the right thing happens when user opts out of GPC and 12 | - that excluded domains, as defined in the privacy remote configuration, are taken into account. 13 | 14 | ## Structure 15 | 16 | Test suite specific fields: 17 | 18 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 19 | - `frameURL` - string - URL of an iframe in which the feature is operating (optional - if not set assume main frame context) 20 | - `requestType` - mostly "image" or "main_frame" (navigational request), but can be any of https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType - type of the resource being fetched 21 | - `gpcUserSettingOn` - boolean - if user controlled GPC setting is on or off (optional - if not set assume on) 22 | - `expectGPCAPI` - boolean - if we expect GPC API to be available in given conditions 23 | - `expectGPCAPIValue` - "true", "false" or "undefined" - stringified expected value of `Navigator.prototype.globalPrivacyControl` 24 | - `expectGPCHeader` - boolean - if we expect GPC header to be included with given request 25 | - `expectGPCHeaderValue` - string - expected value of the `Sec-GPC` header 26 | 27 | ## Pseudo-code implementation 28 | 29 | ``` 30 | loadReferenceConfig('config_reference.json') 31 | 32 | for $testSet in test.json 33 | 34 | for $test in $testSet 35 | if $test.exceptPlatforms includes 'current-platform' 36 | skip 37 | 38 | setSetting('GPC', $test.gpcUserSettingOn or true) 39 | 40 | if $test has 'expectGPCHeader' 41 | $headers = getHeaders($test.siteURL, $test.frameURL, $test.requestType) 42 | $enabled = $headers has 'Sec-GPC' 43 | 44 | expect($enabled === $test.expectGPCHeader) 45 | 46 | if $test has 'expectGPCHeaderValue' 47 | expect(headerValue($headers, 'Sec-GPC') === $test.expectGPCHeaderValue) 48 | 49 | else if $test has 'expectGPCAPI' 50 | $gpcAPIInjected = isGPCInjected($test.siteURL, $test.frameURL) 51 | 52 | expect($gpcAPIInjected === $test.expectGPCAPI) 53 | 54 | if $test has 'expectGPCAPIValue' 55 | expect(getJSPropertyValue('Navigator.prototype.globalPrivacyControl') === $test.expectGPCAPIValue) 56 | 57 | ``` 58 | 59 | ## Platform exceptions 60 | 61 | - On platforms where header modifications is limited headers should only be modified on sites listed in gpcHeaderEnabledSites inside the settings of the gpc feature of the remote config. -------------------------------------------------------------------------------- /global-privacy-control/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration has gpc feature only with some exceptions", 3 | "features": { 4 | "gpc": { 5 | "state": "enabled", 6 | "exceptions": [ 7 | { 8 | "domain": "washingtonpost.com", 9 | "reason": "This is a domain where our iOS/Android apps inject the Sec-GPC request header" 10 | } 11 | ], 12 | "settings": { 13 | "gpcHeaderEnabledSites": [ 14 | "global-privacy-control.glitch.me", 15 | "washingtonpost.com", 16 | "globalprivacycontrol.org", 17 | "example.com" 18 | ] 19 | } 20 | } 21 | }, 22 | "version": 1635943904459, 23 | "unprotectedTemporary": [ 24 | { 25 | "domain": "globalprivacycontrol.org", 26 | "reason": "This exception should disable all features" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /https-upgrades/README.md: -------------------------------------------------------------------------------- 1 | # HTTPS Upgrades 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199103718890894/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of HTTPS upgrading (Smarter Encryption). In particular it focuses on verifying that: 8 | 9 | - domains from the main bloom filter are upgraded, 10 | - domains from the main allowlist are not upgraded, 11 | - domains from the negative bloom filter are not upgraded, 12 | - domains from the negative allowlist are upgraded, 13 | - domains allowlisted in the remote config are not upgraded, 14 | - local urls (localhost and friends) are not upgraded. 15 | 16 | ## Structure 17 | 18 | Files in the folder: 19 | - `config_reference.json` - reference remote config with https exceptions 20 | - `https_bloomfilter_reference.json` and `https_bloomfilter_reference.bin` - the main bloom filter in two formats with hostnames that should be upgraded 21 | - `https_allowlist_reference.json` - allowlist to the main bloom filter with hostnames that should not be upgraded 22 | - `https_bloomfilter_spec_reference.json` - specification of the `.bin` bloom filter 23 | - `https_negative_allowlist_reference.json` - inverse/negative boom filter with hostnames that should not be upgraded 24 | - `https_negative_bloomfilter_reference.json` - allowlist to the negative bloom filter with hostnames that should be upgraded 25 | - `https_upgrade_hostnames.txt` - plaintext list of domains that should be upgraded 26 | - `https_dont_upgrade_hostnames.txt` - plaintext list of domains that should not be upgraded 27 | 28 | Test suite specific fields: 29 | 30 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 31 | - `requestURL` - string - request in question 32 | - `requestType` - mostly "image" or "main_frame" (navigational request), but can be any of https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType - type of the resource being fetched 33 | - `expectURL` - string - expected url after it was upgraded (or not) 34 | 35 | ## Pseudo-code implementation 36 | 37 | ``` 38 | for $testSet in test.json 39 | loadReferenceConfig('config_reference.json') 40 | loadHTTPSBloomFilters( 41 | bloomFilter = 'https_bloomfilter_reference.json', 42 | allowlist = 'https_allowlist_reference.json', 43 | negativeBloomFilter = 'https_negative_bloomfilter_reference.json', 44 | negativeAllowlist = 'https_negative_bloomfilter_reference' 45 | ) 46 | 47 | for $test in $testSet 48 | if $test.exceptPlatforms includes 'current-platform' 49 | skip 50 | 51 | $upgradedRequestURL = upgradeToHTTPS( 52 | siteURL = $test.siteURL, 53 | requestURL = $test.requestURL, 54 | requestType = $test.requestType, 55 | expectURL = $test.expectURL 56 | ) 57 | 58 | expect($upgradedRequestURL === $test.expectURL) 59 | ``` 60 | 61 | ## Bloom filter sources 62 | 63 | Main bloom filter was built based on this list: 64 | 65 | firsttest.com 66 | secondtest.com 67 | secure.thirdtest.com 68 | s.fourthtest.com 69 | fifth-test.com 70 | a.b.c.d.e.sixthtest.com 71 | 72 | And negative bloom filter was built based on this list: 73 | 74 | negativetest.com 75 | s.secondnegative.com 76 | thirdnegative.com 77 | 78 | ## Platform exceptions 79 | 80 | - Android, Windows, iOS and macOS don't support negative bloom filter because it's only needed for platforms where Smarter Encryption API is available 81 | - Android, iOS and macOS don't support updating subrequests because of WebView limitations (request can be cancelled, but not modified) 82 | - Windows don't support updating websocket subrequests because of WebView limitations (request can be cancelled, but not modified) 83 | -------------------------------------------------------------------------------- /https-upgrades/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration includes only the https upgrades feature.", 3 | "features": { 4 | "https": { 5 | "state": "enabled", 6 | "exceptions": [ 7 | { 8 | "domain": "secure.thirdtest.com", 9 | "reason": "requests to this domain should not be upgraded to https" 10 | } 11 | ] 12 | } 13 | }, 14 | "version": 1635943904459, 15 | "unprotectedTemporary": [ 16 | { 17 | "domain": "s.fourthtest.com", 18 | "reason": "requests to this domain should not be upgraded to https" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /https-upgrades/https_allowlist_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | "secondtest.com" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /https-upgrades/https_bloomfilter_reference.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/duckduckgo/privacy-reference-tests/47a71fcdbd1277bc2020d1239945ca6bdb22c74d/https-upgrades/https_bloomfilter_reference.bin -------------------------------------------------------------------------------- /https-upgrades/https_bloomfilter_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "DwSFVVUlFQVNnY1BVUVFVUVBFQVnXa////U=", 3 | "checksum": { 4 | "sha256": "aoWOaRp9ijzNamwQ0sjyKXpVm2Xc0gOrboldDYBHmyA=" 5 | }, 6 | "errorRate": 0.000001, 7 | "totalEntries": 7 8 | } -------------------------------------------------------------------------------- /https-upgrades/https_bloomfilter_spec_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "totalEntries" : 6, 3 | "bitCount" : 173, 4 | "errorRate" : 0.000001, 5 | "sha256" : "27a5d479c9b698d53df5c70583c326035846f8fe239b72edc3a08abcda6bc388" 6 | } -------------------------------------------------------------------------------- /https-upgrades/https_dont_upgrade_hostnames.txt: -------------------------------------------------------------------------------- 1 | negativetest.com 2 | thirdnegative.com 3 | -------------------------------------------------------------------------------- /https-upgrades/https_negative_allowlist_reference.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "data": [ 4 | "s.secondnegative.com" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /https-upgrades/https_negative_bloomfilter_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "cjZTVRdJzc1VVXY=", 3 | "checksum": { 4 | "sha256": "O1oTr9vmotUYFk4+ePDkykLdQSr8XjVYZwU4oVQYM1g=" 5 | }, 6 | "errorRate": 0.000001, 7 | "totalEntries": 3 8 | } -------------------------------------------------------------------------------- /https-upgrades/https_upgrade_hostnames.txt: -------------------------------------------------------------------------------- 1 | firsttest.com 2 | secure.thirdtest.com 3 | s.fourthtest.com 4 | fifth-test.com 5 | a.b.c.d.e.sixthtest.com 6 | s.secondnegative.com 7 | -------------------------------------------------------------------------------- /malicious-site-protection/README.md: -------------------------------------------------------------------------------- 1 | # Feature Name Tests 2 | 3 | Privacy Feature: 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of malicious site protection. In particular it focuses on verifying that: 8 | - local entries stored in the `hashPrefix` and `filterSet` datasets result in blocked navigations 9 | - entries in both the `phishing` and `malware` categories are independently blocked 10 | - entries that should not be blocked are not: ensuring low chance of false positives 11 | - unexpected situations are handled gracefully, i.e. invalid regular expressions, duplicate entries, overlapping data for different categories, etc. 12 | 13 | ## Structure 14 | 15 | Test suite specific fields: 16 | 17 | - `featureName` - string - name of the privacy feature as defined in the config 18 | - `siteURL` - string - the URL of the site we are testing protections for 19 | - `expectURL` - string - the expected canonicalized URL 20 | - `expectDomain` - string - the expected canonicalized domain 21 | - `expectBlock` - bool - true if expected to be blocked, false otherwise 22 | 23 | ## Pseudo-code implementation 24 | 25 | ### Block Test 26 | ``` 27 | for $testSet in block_tests.json 28 | loadReferenceHashPrefixes('hashPrefixes_reference.json') 29 | loadReferenceFilterSet('filterSet_reference.json') 30 | 31 | for $test in $testSet 32 | if $test.exceptPlatforms includes 'current-platform' 33 | skip 34 | 35 | $blocked = MaliciousSiteDetector.evaluate( 36 | url=$test.siteURL, 37 | ) 38 | 39 | expect($blocked === $test.expectBlock) 40 | ``` 41 | 42 | ### Canonicalization Test 43 | 44 | #### URL Canonicalization 45 | 46 | ``` 47 | $testSet = load_tests(canonicalization_test.json).get('urlTests') 48 | for $test in $testSet 49 | if $test.exceptPlatforms includes 'current-platform' 50 | skip 51 | 52 | $canonicalURL = URL.canonicalize( 53 | url=$test.siteURL, 54 | ) 55 | 56 | expect($canonicalURL === $test.expectURL) 57 | ``` 58 | 59 | #### Domain Canonicalization 60 | 61 | ``` 62 | $testSet = load_tests(canonicalization_test.json).get('domainTests') 63 | for $test in $testSet 64 | if $test.exceptPlatforms includes 'current-platform' 65 | skip 66 | 67 | $canonicalDomain = Domain.canonicalize( 68 | url=$test.siteURL, 69 | ) 70 | 71 | expect($canonicalDomain === $test.expectDomain) 72 | ``` 73 | 74 | ## Platform exceptions 75 | 76 | - Currently, malicious site protection is only available on the browser platforms: 77 | - macOS 78 | - iOS 79 | - Android 80 | - Windows 81 | - The feature doesn't make sense to be enabled on extensions, as they will mostly run on browsers that already provide their own malicious site protections. -------------------------------------------------------------------------------- /malicious-site-protection/reference_malware_filterSet.json: -------------------------------------------------------------------------------- 1 | [{"regex": "(?i)^https?\\:\\/\\/malicious.example.com(?:\\:(?:80|443))?\\/login\\/safe\\/urgent\\/notification$", "hash": "e2a8607b1dfd51fc72246aa9a31246e246482441ec26e5838b28c253fe7635c5"}, {"regex": "(?i)^https?\\:\\/\\/\\w+\\.\\w+(?:\\:(?:80|443))?\\/malicious\\.html$", "hash": "a12f78b1a902ccca3491ee69f07d0e68f656c08f49ac41f560c0a25b6f9b2216"}, {"regex": "(?i)^https?\\:\\/\\/malicious-javascript.com(?:\\:(?:80|443))?\\/alert\\/account\\/verify\\/connect\\/file$", "hash": "89ffbd4bab9142ca2d05fed27228bd0b73f02b9980ba8beb023409a0400f7d45"}, {"regex": ".", "hash": "bef8479e07d45839982dab1279cd8eb2181a4a4d62be24f1d2d85baf39dae526"}, {"regex": "(?i)^https?\\:\\/\\/danger.malware.distribution.com(?:\\:(?:80|443))?\\/support\\/confirm\\/safety\\/login\\/notification\\/password\\/password\\/information\\/information$", "hash": "c0af9f76dce48df2408edbcc96860e24848bd918a3c8d3656d6dc361a642397b"}] -------------------------------------------------------------------------------- /malicious-site-protection/reference_malware_hashPrefixes.json: -------------------------------------------------------------------------------- 1 | ["e2a8607b", "a12f78b1", "89ffbd4b", "bef8479e", "c0af9f76"] -------------------------------------------------------------------------------- /malicious-site-protection/reference_phishing_filterSet.json: -------------------------------------------------------------------------------- 1 | [{"regex": ".", "hash": "e2a8607b1dfd51fc72246aa9a31246e246482441ec26e5838b28c253fe7635c5"}, {"regex": "(?i)^https?\\:\\/\\/stealing.your.data.co.uk(?:\\:(?:80|443))?\\/loading\\/login\\/connect\\/alert\\/download\\/information\\/your\\/transaction$", "hash": "41be5794b4ce63c0c529f6d87eb0cdd1887b65fdfaef99cf18d90c5ef8f9d768"}, {"regex": "(?i)^https?\\:\\/\\/phishing-site.com(?:\\:(?:80|443))?\\/download\\/transaction\\/safety\\/check$", "hash": "1be002268941e7c2e63394701793e9526f5d0dd64393ea9712317f2e70348c08"}, {"regex": "(?i)^https?\\:\\/\\/impersonating-gov-uk.co.uk(?:\\:(?:80|443))?\\/login\\/protect\\/account\\/account\\/data\\/account\\/urgent\\/service\\/install\\/support$", "hash": "aab7d9fcb9a26e65b4feec8216a7bdb179f884ab7f14096c95ec2cd96f3999bf"}, {"regex": ".", "hash": "5dd01a6a826584303a07cf3eebe027b0827275943488930c95c4718ffe34a56d"}] -------------------------------------------------------------------------------- /malicious-site-protection/reference_phishing_hashPrefixes.json: -------------------------------------------------------------------------------- 1 | ["e2a8607b", "41be5794", "1be00226", "aab7d9fc", "5dd01a6a"] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@duckduckgo/privacy-reference-tests", 3 | "description": "Test metadata used by DuckDuckGo apps and extensions to verify implementation of privacy features.", 4 | "license": "Apache-2.0", 5 | "repository": "duckduckgo/privacy-reference-tests" 6 | } 7 | -------------------------------------------------------------------------------- /privacy-configuration/README.MD: -------------------------------------------------------------------------------- 1 | # Privacy Configuration Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199981227466169/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of privacy configuration. In particular it focuses on verifying that: 8 | 9 | - features are correctly enabled / disabled according to the global setting ("state"), 10 | - exceptions are taken into account ("exceptions", "unprotectedTemporary") and url matching is according to the spec ("domain"), 11 | - features are correctly injected, or not, into iframes ("aboutBlank", "aboutBlankSites"), 12 | - features are correctly disabled for specific scripts ("script"), 13 | - features missing from the config are disabled and don't cause a crash. 14 | 15 | ## Structure 16 | 17 | There are multiple sets of tests in `tests.json` and multiple config files. Each set defines what configuration file (`configX_reference.json`) is expected to be loaded via "referenceConfig" field. 18 | 19 | Test suite specific fields: 20 | 21 | - featureName - string - name of the privacy feature as defined in the config 22 | - siteURL - string - currently loaded website's URL (as seen in the URL bar) 23 | - frameURL - string - URL of an iframe in which the feature is operating (optional - if not set assume main frame context) 24 | - scriptURL - string - URL of script that is trying to use a protected feature (optional - if not set assume no script URL) 25 | - expectFeatureEnabled - bool - if feature is expected to be disabled or not 26 | 27 | ## Pseudo-code implementation 28 | 29 | ``` 30 | for $testSet in test.json 31 | loadRemoteConfig($testSet.referenceConfig) 32 | 33 | for $test in $testSet 34 | $enabled = isEnabled( 35 | feature=$test.featureName, 36 | url=$test.siteURL, 37 | frame=$test.frameURL, 38 | script=$test.scriptURL 39 | ) 40 | 41 | expect($enabled === $test.expectFeatureEnabled) 42 | ``` -------------------------------------------------------------------------------- /privacy-configuration/config3_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration is missing features.", 3 | "features": { 4 | }, 5 | "version": 1635943904459, 6 | "unprotectedTemporary": [] 7 | } -------------------------------------------------------------------------------- /referrer-trimming/README.md: -------------------------------------------------------------------------------- 1 | # Referrer Trimming Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1199093921854084/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of referrer trimming protection. In particular it focuses on verifying that: 8 | 9 | - referrer is modified (trimmed) correctly in various scenarios, 10 | - that both header (`Referer`) and the API (`document.referrer`) are modified and 11 | - that excluded domains (via remote config or turning off protections by the user) are taken into account. 12 | 13 | ## Structure 14 | 15 | Test suite specific fields: 16 | 17 | - `navigatingFromURL` - string - navigation initiator URL 18 | - `navigatingToURL` - string - navigation destination URL 19 | - `referrerValue` - string - value of the referrer (both header and JS API) 20 | - `expectReferrerHeaderValue` - expected value of the "Referer" header after it was processed 21 | - `siteURL` - string - currently loaded website's URL (as seen in the URL bar) 22 | - `frameURL` - string - URL of an iframe in which the feature is operating (optional - if not set assume main frame context) 23 | - `requestURL` - string - URL to a request being made 24 | - `requestType` - mostly "script" or "main_frame" (navigational request), but can be any of https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType - type of the resource being fetched 25 | - `expectReferrerAPIValue` - expected value of JS API (`document.referrer`) after it was processed 26 | 27 | ## Pseudo-code implementation 28 | 29 | ``` 30 | loadReferenceConfig('config_reference.json') 31 | loadReferenceBlocklist('tracker_radar_reference.json') 32 | 33 | for $testSet in test.json 34 | 35 | for $test in $testSet 36 | if $test.exceptPlatforms includes 'current-platform' 37 | skip 38 | 39 | if ("navigatingFromURL" in $test) { 40 | // testing navigations 41 | $modifiedReferrer = trimHeaders( 42 | navigatingFrom = $test.navigatingFromURL, 43 | navigatingTo = $test.navigatingToURL, 44 | headers = ["Referer" => $test.referrerValue] 45 | ) 46 | 47 | expect($modifiedReferrer === $test.expectReferrerHeaderValue) 48 | } else if ("requestURL" in $test) { 49 | // testing subrequests 50 | $modifiedReferrer = trimHeaders( 51 | siteURL = $test.siteURL, 52 | requestURL = $test.requestURL, 53 | requestType = $test.requestType, 54 | headers = ["Referer" => $test.referrerValue] 55 | ) 56 | 57 | expect($modifiedReferrer === $test.expectReferrerHeaderValue) 58 | } else if ("expectReferrerAPIValue" in $test) { 59 | // testing JS API 60 | $modifiedReferrer = documentReferrerValue( 61 | siteURL = $test.siteURL, 62 | frameURL = $test.frameURL, 63 | initialValue = $test.referrerValue 64 | ) 65 | 66 | expect($modifiedReferrer === $test.expectReferrerAPIValue) 67 | } 68 | ``` 69 | -------------------------------------------------------------------------------- /referrer-trimming/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration includes only the referrer trimming feature.", 3 | "features": { 4 | "referrer": { 5 | "state": "enabled", 6 | "exceptions": [ 7 | { 8 | "domain": "keep-referrer.com", 9 | "reason": "referrer header, or JS API, should not be modified" 10 | } 11 | ] 12 | } 13 | }, 14 | "version": 1635943904459, 15 | "unprotectedTemporary": [ 16 | { 17 | "domain": "global-keep-referrer.com", 18 | "reason": "referrer header, or JS API, should not be modified" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /referrer-trimming/tracker_radar_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackers": { 3 | "tracker.com": { 4 | "domain": "tracker.com", 5 | "owner": { 6 | "name": "Tracker INC", 7 | "displayName": "Tracker INC", 8 | "privacyPolicy": "https://tracker.com/lies", 9 | "url": "https://tracker.com/" 10 | }, 11 | "prevalence": 0.1, 12 | "fingerprinting": 3, 13 | "cookies": 0.1, 14 | "categories": [], 15 | "default": "block", 16 | "rules": [] 17 | }, 18 | "unblockable-tracker.net": { 19 | "domain": "unblockable-tracker.net", 20 | "owner": { 21 | "name": "Unblockable Tracker INC", 22 | "displayName": "Unblockable Tracker INC", 23 | "privacyPolicy": "https://unblockable-tracker.net/lies", 24 | "url": "https://unblockable-tracker.net/" 25 | }, 26 | "prevalence": 0.1, 27 | "fingerprinting": 3, 28 | "cookies": 0.1, 29 | "categories": [], 30 | "default": "ignore", 31 | "rules": [] 32 | } 33 | }, 34 | "entities": { 35 | "Company 1": { 36 | "domains": [ 37 | "company1.com", 38 | "company1.co.uk" 39 | ], 40 | "prevalence": 0.1, 41 | "displayName": "Company 1" 42 | }, 43 | "Company 2": { 44 | "domains": [ 45 | "company2.com", 46 | "company2.co.uk" 47 | ], 48 | "prevalence": 0.1, 49 | "displayName": "Company 2" 50 | }, 51 | "Tracker INC": { 52 | "domains": [ 53 | "tracker.com", 54 | "tracker.test" 55 | ], 56 | "prevalence": 0.1, 57 | "displayName": "Tracker INC" 58 | }, 59 | "Unblockable Tracker INC": { 60 | "domains": [ 61 | "unblockable-tracker.net" 62 | ], 63 | "prevalence": 0.1, 64 | "displayName": "Unblockable Tracker INC" 65 | } 66 | }, 67 | "cnames": { 68 | "tracker.cname.com": "bad.tracker.com" 69 | }, 70 | "domains": { 71 | "company1.com": "Company 1", 72 | "company1.co.uk": "Company 1", 73 | "company2.com": "Company 2", 74 | "company2.co.uk": "Company 2", 75 | "tracker.com": "Tracker INC", 76 | "tracker.test": "Tracker INC", 77 | "unblockable-tracker.net": "Unblockable Tracker INC" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /storage-clearing/README.md: -------------------------------------------------------------------------------- 1 | # Storage Clearing (Fire Button & Fireproofing) 2 | 3 | Privacy Feature: https://app.asana.com/0/0/1201277458549583/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of fire button and fireproofing. In particular it focuses on verifying that: 8 | 9 | - cookies are cleared for non-fireproofed sites, 10 | - cookies are NOT cleared for fireproofed sites, 11 | - some duckduckgo.com setting cookies are not cleared. 12 | 13 | ## Structure 14 | 15 | Test suite specific fields: 16 | 17 | - `fireproofedSites` - array of strings - list of sites that user fireproofed 18 | - `cookieDomain` - string - domain on which cookie is set 19 | - `cookieName` - string - name of the cookie being set 20 | - `expectCookieRemoved` - bool - if cookie is expected to be cleared or not after fire button is clicked 21 | 22 | ## Pseudo-code implementation 23 | 24 | ``` 25 | for $testSet in test.json 26 | 27 | for $site in $testSet.fireproofedSites 28 | fireproof($site) 29 | 30 | for $test in $testSet 31 | if $test.exceptPlatforms includes 'current-platform' 32 | skip 33 | 34 | createCookie( 35 | domain = $test.cookieDomain, 36 | name = $test.cookieName 37 | value = "123" 38 | ) 39 | 40 | fireButton() 41 | 42 | $cookie = getCookie( 43 | domain = $test.cookieDomain, 44 | name = $test.cookieName 45 | ) 46 | 47 | expect($cookie === null).toBe($test.expectCookieRemoved) 48 | ``` 49 | -------------------------------------------------------------------------------- /storage-clearing/tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "fireButtonFireproofing": { 3 | "name": "Fire Button & Fireproofing", 4 | "desc": "Fire button should clear all storage unless site is fireproofed", 5 | "fireproofedSites": [ 6 | "https://fireproofed.com/", 7 | "https://sub.no-fire.com/path/file.html?query=value#frag", 8 | "fireproofed.co.uk", 9 | "a.github.io" 10 | ], 11 | "tests": [ 12 | { 13 | "name": "unfireproofed domain - cookie should be cleared", 14 | "cookieDomain": ".example.com", 15 | "cookieName": "test", 16 | "expectCookieRemoved": true, 17 | "exceptPlatforms": [] 18 | }, 19 | { 20 | "name": "unfireproofed domain - cookie should be cleared", 21 | "cookieDomain": "example.co.uk", 22 | "cookieName": "test", 23 | "expectCookieRemoved": true, 24 | "exceptPlatforms": [] 25 | }, 26 | { 27 | "name": "unfireproofed domain - cookie should be cleared", 28 | "cookieDomain": "b.github.io", 29 | "cookieName": "test", 30 | "expectCookieRemoved": true, 31 | "exceptPlatforms": [] 32 | }, 33 | { 34 | "name": "fireproofed domain - cookie should not be cleared", 35 | "cookieDomain": "fireproofed.co.uk", 36 | "cookieName": "test", 37 | "expectCookieRemoved": false, 38 | "exceptPlatforms": [] 39 | }, 40 | { 41 | "name": "fireproofed domain - cookie should not be cleared", 42 | "cookieDomain": "a.github.io", 43 | "cookieName": "test", 44 | "expectCookieRemoved": false, 45 | "exceptPlatforms": [] 46 | }, 47 | { 48 | "name": "subdomain is fireproofed - cookie should not be cleared", 49 | "cookieDomain": "no-fire.com", 50 | "cookieName": "test", 51 | "expectCookieRemoved": false, 52 | "exceptPlatforms": [ 53 | "ios-browser", 54 | "android-browser", 55 | "windows-browser" 56 | ] 57 | }, 58 | { 59 | "name": "subdomain is fireproofed, cookie is using dot notation - it should not be cleared", 60 | "cookieDomain": ".no-fire.com", 61 | "cookieName": "test", 62 | "expectCookieRemoved": false, 63 | "exceptPlatforms": [] 64 | }, 65 | { 66 | "name": "fireproofed subdomain match - cookie should not be cleared", 67 | "cookieDomain": "sub.no-fire.com", 68 | "cookieName": "test", 69 | "expectCookieRemoved": false, 70 | "exceptPlatforms": [] 71 | }, 72 | { 73 | "name": "fireproofed subdomain match, cookie is using dot notation - cookie should not be cleared", 74 | "cookieDomain": ".sub.no-fire.com", 75 | "cookieName": "test", 76 | "expectCookieRemoved": false, 77 | "exceptPlatforms": [ 78 | "ios-browser", 79 | "android-browser", 80 | "windows-browser" 81 | ] 82 | }, 83 | { 84 | "name": "subdomain for the fireproofed hostname - cookie should not be cleared", 85 | "cookieDomain": "sub.sub.no-fire.com", 86 | "cookieName": "test", 87 | "expectCookieRemoved": false, 88 | "exceptPlatforms": [ 89 | "ios-browser", 90 | "android-browser", 91 | "windows-browser" 92 | ] 93 | }, 94 | { 95 | "name": "cookie is set on a subdomain of a fireproofed domain - cookie should not be cleared", 96 | "cookieDomain": "sub.fireproofed.com", 97 | "cookieName": "test", 98 | "expectCookieRemoved": false, 99 | "exceptPlatforms": [ 100 | "ios-browser", 101 | "android-browser", 102 | "windows-browser" 103 | ] 104 | }, 105 | { 106 | "name": "fireproofing match, cookie is using dot notation - cookie should not be cleared", 107 | "cookieDomain": ".fireproofed.com", 108 | "cookieName": "test", 109 | "expectCookieRemoved": false, 110 | "exceptPlatforms": [] 111 | }, 112 | { 113 | "name": "duckduckgo.com settings cookie - cookie should not be cleared", 114 | "cookieDomain": "duckduckgo.com", 115 | "cookieName": "setting", 116 | "expectCookieRemoved": false, 117 | "exceptPlatforms": [] 118 | } 119 | ] 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /suggestions/README.md: -------------------------------------------------------------------------------- 1 | # Suggestions Tests 2 | 3 | [Feature: Shared Address Bar Suggestions algorithm Tests for macOS and Windows](https://app.asana.com/0/1202406491309510/1209464112525114) 4 | 5 | ## Goals 6 | 7 | This set of tests verifies implementation of address bar suggestions across platforms. In particular it focuses on verifying that: 8 | 9 | - correct suggestions are shown based on user input 10 | - suggestions are properly filtered and ordered 11 | 12 | ## Structure 13 | 14 | Test suite specific fields: 15 | 16 | - `description` - string - describes the test case scenario 17 | - `input` - object containing: 18 | - `query` - string - user input in the address bar 19 | - `tabIdInitiatingSearch` - string (UUID) - ID of tab where search is initiated 20 | - `bookmarks` - array of bookmark objects with title, url, and isFavorite properties 21 | - `history` - array of history entry objects 22 | - `pinnedTabs` - array of pinned tab objects with tabId, title and uri 23 | - `windows` - array of window objects containing: 24 | - `type` - string - window type ("fullyFeatured", "fireWindow", "popup") 25 | - `tabs` - array of tab objects with tabId, title and uri 26 | - `apiSuggestions` - array of API suggestion results or error object 27 | - `expectation` - object containing: 28 | - `topHits` - array of expected top hit suggestions (best matches across all sources) 29 | - `searchSuggestions` - array of expected DuckDuckGo API suggestions 30 | - `localSuggestions` - array of expected local suggestions (bookmarks, history, tabs, settings pages) 31 | 32 | ## Pseudo-code implementation 33 | 34 | for $testSet in tests.json 35 | initializeTestEnvironment( 36 | bookmarks = $testSet.input.bookmarks, 37 | history = $testSet.input.history, 38 | pinnedTabs = $testSet.input.pinnedTabs, 39 | windows = $testSet.input.windows 40 | ) 41 | 42 | mockAPIResponse($testSet.input.apiSuggestions) 43 | 44 | $suggestions = getSuggestions(query = $testSet.input.query) 45 | 46 | expect($suggestions.topHits).toEqual($testSet.expectation.topHits) 47 | expect($suggestions.searchSuggestions).toEqual($testSet.expectation.searchSuggestions) 48 | expect($suggestions.localSuggestions).toEqual($testSet.expectation.localSuggestions) 49 | -------------------------------------------------------------------------------- /suggestions/comprehensive-mixed-sources-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Comprehensive test with multiple data sources including bookmarks, favorites, history, tabs, and API suggestions", 4 | "input": { 5 | "query": "duck", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { "phrase": "duckduckgo" }, 9 | { "phrase": "duck dynasty" }, 10 | { "phrase": "duck tape" }, 11 | { "phrase": "duck soup" }, 12 | { "phrase": "duck tales" }, 13 | { "phrase": "duck hunting season" }, 14 | { "phrase": "duck typing" }, 15 | { "phrase": "duck duck goose game" } 16 | ], 17 | "bookmarks": [ 18 | { 19 | "uri": "https://duckduckgo.com/app", 20 | "title": "Download DuckDuckGo App", 21 | "isFavorite": false 22 | }, 23 | { 24 | "uri": "https://duck.com/help", 25 | "title": "DuckDuckGo Help Center", 26 | "isFavorite": false 27 | }, 28 | { 29 | "uri": "https://duckduckgo.com/spread", 30 | "title": "Spread DuckDuckGo", 31 | "isFavorite": true 32 | } 33 | ], 34 | "history": [ 35 | { 36 | "uri": "https://duckduckgo.com/", 37 | "title": "DuckDuckGo — Privacy, simplified.", 38 | "visitCount": 45 39 | }, 40 | { 41 | "uri": "https://duckduckgo.com/settings", 42 | "title": "DuckDuckGo Settings", 43 | "visitCount": 5 44 | }, 45 | { 46 | "uri": "https://duckduckgo.com/privacy", 47 | "title": "Privacy Policy – DuckDuckGo", 48 | "visitCount": 2 49 | }, 50 | { 51 | "uri": "https://duckduckgo.com/about", 52 | "title": "About DuckDuckGo", 53 | "visitCount": 1 54 | }, 55 | { 56 | "uri": "https://duck.com/", 57 | "title": "DuckDuckGo — Privacy, simplified.", 58 | "visitCount": 3 59 | }, 60 | { 61 | "uri": "https://duckduckg.com/", 62 | "title": "DuckDuckGo (typo)", 63 | "visitCount": 1, 64 | "failedToLoad": true 65 | }, 66 | { 67 | "uri": "https://theduckwebsite.com/", 68 | "title": "The Duck Website - All About Ducks", 69 | "visitCount": 1 70 | } 71 | ], 72 | "pinnedTabs": [], 73 | "windows": [ 74 | { 75 | "type": "fullyFeatured", 76 | "tabs": [ 77 | { 78 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a", 79 | "uri": "https://duckduckgo.com/settings", 80 | "title": "DuckDuckGo Settings" 81 | }, 82 | { 83 | "tabId": "eaf6e9de-5376-4f9c-9b4d-79a6b1135bb5", 84 | "uri": "https://duckduckgo.com/app?origin=funnel_app_ios", 85 | "title": "Download the DuckDuckGo app" 86 | }, 87 | { 88 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 89 | "uri": "duck://newtab", 90 | "title": "New Tab" 91 | } 92 | ] 93 | } 94 | ], 95 | "ignoredUris": [ 96 | "duckduckg.com" 97 | ] 98 | }, 99 | "expectations": { 100 | "topHits": [ 101 | { 102 | "type": "historyEntry", 103 | "title": "DuckDuckGo — Privacy, simplified.", 104 | "subtitle": "", 105 | "uri": "https://duckduckgo.com/", 106 | "score": 2355245 107 | }, 108 | { 109 | "type": "historyEntry", 110 | "title": "DuckDuckGo — Privacy, simplified.", 111 | "subtitle": "", 112 | "uri": "https://duck.com/", 113 | "score": 2355203 114 | } 115 | ], 116 | "searchSuggestions": [ 117 | { 118 | "type": "phrase", 119 | "title": "duckduckgo", 120 | "uri": "https://duckduckgo.com/?q=duckduckgo", 121 | "score": 0 122 | }, 123 | { 124 | "type": "phrase", 125 | "title": "duck dynasty", 126 | "uri": "https://duckduckgo.com/?q=duck+dynasty", 127 | "score": 0 128 | }, 129 | { 130 | "type": "phrase", 131 | "title": "duck tape", 132 | "uri": "https://duckduckgo.com/?q=duck+tape", 133 | "score": 0 134 | }, 135 | { 136 | "type": "phrase", 137 | "title": "duck soup", 138 | "uri": "https://duckduckgo.com/?q=duck+soup", 139 | "score": 0 140 | }, 141 | { 142 | "type": "phrase", 143 | "title": "duck tales", 144 | "uri": "https://duckduckgo.com/?q=duck+tales", 145 | "score": 0 146 | }, 147 | { 148 | "type": "phrase", 149 | "title": "duck hunting season", 150 | "uri": "https://duckduckgo.com/?q=duck+hunting+season", 151 | "score": 0 152 | }, 153 | { 154 | "type": "phrase", 155 | "title": "duck typing", 156 | "uri": "https://duckduckgo.com/?q=duck+typing", 157 | "score": 0 158 | } 159 | ], 160 | "localSuggestions": [ 161 | { 162 | "type": "openTab", 163 | "title": "DuckDuckGo Settings", 164 | "subtitle": "", 165 | "uri": "https://duckduckgo.com/settings", 166 | "score": 307205, 167 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a" 168 | }, 169 | { 170 | "type": "historyEntry", 171 | "title": "Privacy Policy – DuckDuckGo", 172 | "subtitle": "", 173 | "uri": "https://duckduckgo.com/privacy", 174 | "score": 307202 175 | }, 176 | { 177 | "type": "historyEntry", 178 | "title": "About DuckDuckGo", 179 | "subtitle": "", 180 | "uri": "https://duckduckgo.com/about", 181 | "score": 307201 182 | } 183 | ] 184 | }, 185 | "platform": "mobile" 186 | } 187 | -------------------------------------------------------------------------------- /suggestions/edge-cases-diacritic-case-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Tests edge cases including diacritics, case sensitivity, and partial url matches", 4 | "input": { 5 | "query": "café", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { "phrase": "café near me" }, 9 | { "phrase": "café du monde" }, 10 | { "phrase": "café au lait" }, 11 | { "phrase": "café bustelo" }, 12 | { "phrase": "café con leche" }, 13 | { "phrase": "café astrology" } 14 | ], 15 | "bookmarks": [ 16 | { 17 | "uri": "https://www.cafeterianewyork.com/", 18 | "title": "Caféteria New York", 19 | "isFavorite": true 20 | }, 21 | { 22 | "uri": "https://www.cafehabana.com/", 23 | "title": "Cafe Habana", 24 | "isFavorite": false 25 | }, 26 | { 27 | "uri": "https://www.cafedemadrid.es/", 28 | "title": "CAFÉ de MADRID", 29 | "isFavorite": false 30 | } 31 | ], 32 | "history": [ 33 | { 34 | "uri": "https://cafe.com/", 35 | "title": "Café - Official Site", 36 | "visitCount": 5 37 | }, 38 | { 39 | "uri": "https://www.starbucks.com/", 40 | "title": "Starbucks Coffee Company", 41 | "visitCount": 10 42 | }, 43 | { 44 | "uri": "https://www.cafedirect.co.uk/", 45 | "title": "Cafédirect | Fairtrade Coffee, Tea & Hot Chocolate", 46 | "visitCount": 2 47 | }, 48 | { 49 | "uri": "https://www.bluebottlecoffee.com/", 50 | "title": "Blue Bottle Coffee | Specialty Coffee, Beans & Café", 51 | "visitCount": 3 52 | }, 53 | { 54 | "uri": "https://cafepress.com/", 55 | "title": "CafePress: Custom T-Shirts, Gifts & More", 56 | "visitCount": 1 57 | } 58 | ], 59 | "pinnedTabs": [], 60 | "windows": [ 61 | { 62 | "type": "fullyFeatured", 63 | "tabs": [ 64 | { 65 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a", 66 | "uri": "https://www.cafedelites.com/", 67 | "title": "Cafe Delites | Easy Healthy Recipes" 68 | }, 69 | { 70 | "tabId": "eaf6e9de-5376-4f9c-9b4d-79a6b1135bb5", 71 | "uri": "https://en.wikipedia.org/wiki/Caf%C3%A9", 72 | "title": "Café - Wikipedia" 73 | }, 74 | { 75 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 76 | "uri": "duck://newtab", 77 | "title": "New Tab" 78 | } 79 | ] 80 | } 81 | ], 82 | "ignoredUris": [] 83 | }, 84 | "expectations": { 85 | "topHits": [ 86 | { 87 | "type": "historyEntry", 88 | "title": "Café - Official Site", 89 | "subtitle": "", 90 | "uri": "https://cafe.com/", 91 | "score": 2252805 92 | }, 93 | { 94 | "type": "historyEntry", 95 | "title": "Cafédirect | Fairtrade Coffee, Tea & Hot Chocolate", 96 | "subtitle": "", 97 | "uri": "https://www.cafedirect.co.uk/", 98 | "score": 2252802 99 | } 100 | ], 101 | "searchSuggestions": [ 102 | { 103 | "type": "phrase", 104 | "title": "café near me", 105 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+near+me", 106 | "score": 0 107 | }, 108 | { 109 | "type": "phrase", 110 | "title": "café du monde", 111 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+du+monde", 112 | "score": 0 113 | }, 114 | { 115 | "type": "phrase", 116 | "title": "café au lait", 117 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+au+lait", 118 | "score": 0 119 | }, 120 | { 121 | "type": "phrase", 122 | "title": "café bustelo", 123 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+bustelo", 124 | "score": 0 125 | }, 126 | { 127 | "type": "phrase", 128 | "title": "café con leche", 129 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+con+leche", 130 | "score": 0 131 | }, 132 | { 133 | "type": "phrase", 134 | "title": "café astrology", 135 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+astrology", 136 | "score": 0 137 | } 138 | ], 139 | "localSuggestions": [ 140 | { 141 | "type": "bookmark", 142 | "title": "CAFÉ de MADRID", 143 | "subtitle": "", 144 | "uri": "https://www.cafedemadrid.es/", 145 | "score": 2252800 146 | }, 147 | { 148 | "type": "favorite", 149 | "title": "Caféteria New York", 150 | "subtitle": "", 151 | "uri": "https://www.cafeterianewyork.com/", 152 | "score": 2252800 153 | }, 154 | { 155 | "type": "openTab", 156 | "title": "Café - Wikipedia", 157 | "subtitle": "", 158 | "uri": "https://en.wikipedia.org/wiki/Caf%C3%A9", 159 | "score": 204800, 160 | "tabId": "eaf6e9de-5376-4f9c-9b4d-79a6b1135bb5" 161 | } 162 | ] 163 | }, 164 | "platform": "mobile" 165 | } 166 | -------------------------------------------------------------------------------- /suggestions/edge-cases-diacritic-case.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Tests edge cases including diacritics, case sensitivity, and partial url matches", 4 | "input": { 5 | "query": "café", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { "phrase": "café near me" }, 9 | { "phrase": "café du monde" }, 10 | { "phrase": "café au lait" }, 11 | { "phrase": "café bustelo" }, 12 | { "phrase": "café con leche" }, 13 | { "phrase": "café astrology" } 14 | ], 15 | "bookmarks": [ 16 | { 17 | "uri": "https://www.cafeterianewyork.com/", 18 | "title": "Caféteria New York", 19 | "isFavorite": true 20 | }, 21 | { 22 | "uri": "https://www.cafehabana.com/", 23 | "title": "Cafe Habana", 24 | "isFavorite": false 25 | }, 26 | { 27 | "uri": "https://www.cafedemadrid.es/", 28 | "title": "CAFÉ de MADRID", 29 | "isFavorite": false 30 | } 31 | ], 32 | "history": [ 33 | { 34 | "uri": "https://cafe.com/", 35 | "title": "Café - Official Site", 36 | "visitCount": 5 37 | }, 38 | { 39 | "uri": "https://www.starbucks.com/", 40 | "title": "Starbucks Coffee Company", 41 | "visitCount": 10 42 | }, 43 | { 44 | "uri": "https://www.cafedirect.co.uk/", 45 | "title": "Cafédirect | Fairtrade Coffee, Tea & Hot Chocolate", 46 | "visitCount": 2 47 | }, 48 | { 49 | "uri": "https://www.bluebottlecoffee.com/", 50 | "title": "Blue Bottle Coffee | Specialty Coffee, Beans & Café", 51 | "visitCount": 3 52 | }, 53 | { 54 | "uri": "https://cafepress.com/", 55 | "title": "CafePress: Custom T-Shirts, Gifts & More", 56 | "visitCount": 1 57 | } 58 | ], 59 | "pinnedTabs": [], 60 | "windows": [ 61 | { 62 | "type": "fullyFeatured", 63 | "tabs": [ 64 | { 65 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 66 | "uri": "duck://newtab", 67 | "title": "New Tab" 68 | } 69 | ] 70 | }, 71 | { 72 | "type": "fullyFeatured", 73 | "tabs": [ 74 | { 75 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a", 76 | "uri": "https://www.cafedelites.com/", 77 | "title": "Cafe Delites | Easy Healthy Recipes" 78 | } 79 | ] 80 | }, 81 | { 82 | "type": "fullyFeatured", 83 | "tabs": [ 84 | { 85 | "tabId": "eaf6e9de-5376-4f9c-9b4d-79a6b1135bb5", 86 | "uri": "https://en.wikipedia.org/wiki/Caf%C3%A9", 87 | "title": "Café - Wikipedia" 88 | } 89 | ] 90 | } 91 | ], 92 | "ignoredUris": [] 93 | }, 94 | "expectations": { 95 | "topHits": [ 96 | { 97 | "type": "historyEntry", 98 | "title": "Café - Official Site", 99 | "subtitle": "cafe.com", 100 | "uri": "https://cafe.com/", 101 | "score": 2252805 102 | }, 103 | { 104 | "type": "historyEntry", 105 | "title": "Cafédirect | Fairtrade Coffee, Tea & Hot Chocolate", 106 | "subtitle": "cafedirect.co.uk", 107 | "uri": "https://www.cafedirect.co.uk/", 108 | "score": 2252802 109 | } 110 | ], 111 | "searchSuggestions": [ 112 | { 113 | "type": "phrase", 114 | "title": "café near me", 115 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+near+me", 116 | "score": 0 117 | }, 118 | { 119 | "type": "phrase", 120 | "title": "café du monde", 121 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+du+monde", 122 | "score": 0 123 | }, 124 | { 125 | "type": "phrase", 126 | "title": "café au lait", 127 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+au+lait", 128 | "score": 0 129 | }, 130 | { 131 | "type": "phrase", 132 | "title": "café bustelo", 133 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+bustelo", 134 | "score": 0 135 | }, 136 | { 137 | "type": "phrase", 138 | "title": "café con leche", 139 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+con+leche", 140 | "score": 0 141 | }, 142 | { 143 | "type": "phrase", 144 | "title": "café astrology", 145 | "uri": "https://duckduckgo.com/?q=caf%C3%A9+astrology", 146 | "score": 0 147 | } 148 | ], 149 | "localSuggestions": [ 150 | { 151 | "type": "favorite", 152 | "title": "Caféteria New York", 153 | "subtitle": "cafeterianewyork.com", 154 | "uri": "https://www.cafeterianewyork.com/", 155 | "score": 2252800 156 | }, 157 | { 158 | "type": "bookmark", 159 | "title": "CAFÉ de MADRID", 160 | "subtitle": "cafedemadrid.es", 161 | "uri": "https://www.cafedemadrid.es/", 162 | "score": 2252800 163 | }, 164 | { 165 | "type": "openTab", 166 | "title": "Café - Wikipedia", 167 | "subtitle": "en.wikipedia.org/wiki/Caf%C3%A9", 168 | "uri": "https://en.wikipedia.org/wiki/Caf%C3%A9", 169 | "score": 204800, 170 | "tabId": "eaf6e9de-5376-4f9c-9b4d-79a6b1135bb5" 171 | } 172 | ] 173 | }, 174 | "platform": "desktop" 175 | } 176 | -------------------------------------------------------------------------------- /suggestions/history-nav-link-dont-dedupe-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "mobile", 3 | "description": "History url matches nav link, should not dedupe nav link", 4 | "input": { 5 | "query": "alj", 6 | "tabIdInitiatingSearch": "f49f851c-4627-4154-b26d-3d544b2658e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "aljerraza english" 10 | }, 11 | { 12 | "phrase": "aljazeera.com", 13 | "isNav": true 14 | }, 15 | { 16 | "phrase": "aljamain sterling" 17 | }, 18 | { 19 | "phrase": "aljerraza.english.com", 20 | "isNav": true 21 | }, 22 | { 23 | "phrase": "aljazeera net english live" 24 | }, 25 | { 26 | "phrase": "aljazeera net arabic" 27 | }, 28 | { 29 | "phrase": "aljerraza english live" 30 | }, 31 | { 32 | "phrase": "aljerezza news" 33 | }, 34 | { 35 | "phrase": "aljona savchenko" 36 | }, 37 | { 38 | "phrase": "alj discussion forum" 39 | } 40 | ], 41 | "bookmarks": [], 42 | "history": [ 43 | { 44 | "uri": "https://duckduckgo.com/", 45 | "title": "DuckDuckGo - Protection. Privacy. Peace of mind.", 46 | "visitCount": 1 47 | }, 48 | { 49 | "uri": "https://www.aljazeera.com/", 50 | "title": "Breaking News, World News and Video from Al Jazeera", 51 | "visitCount": 3 52 | }, 53 | { 54 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 55 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 56 | "visitCount": 1 57 | } 58 | ], 59 | "pinnedTabs": [], 60 | "windows": [ 61 | { 62 | "windowId": "a3be9d7e-f27c-4e59-8d04-cba6805f8fcf", 63 | "type": "fullyFeatured", 64 | "tabs": [ 65 | { 66 | "tabId": "f49f851c-4627-4154-b26d-3d544b2658e2", 67 | "uri": "duck://newtab", 68 | "title": "New Tab" 69 | } 70 | ] 71 | } 72 | ], 73 | "ignoredUris": [] 74 | }, 75 | "expectations": { 76 | "topHits": [ 77 | { 78 | "type": "historyEntry", 79 | "title": "Breaking News, World News and Video from Al Jazeera", 80 | "subtitle": "", 81 | "uri": "https://www.aljazeera.com/", 82 | "score": 2355203 83 | }, 84 | { 85 | "score": 0, 86 | "subtitle": "", 87 | "title": "aljazeera.com", 88 | "type": "website", 89 | "uri": "http://aljazeera.com" 90 | } 91 | ], 92 | "searchSuggestions": [ 93 | { 94 | "type": "phrase", 95 | "title": "aljerraza english", 96 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish", 97 | "score": 0 98 | }, 99 | { 100 | "type": "phrase", 101 | "title": "aljamain sterling", 102 | "uri": "https://duckduckgo.com/?q=aljamain\u002Bsterling", 103 | "score": 0 104 | }, 105 | { 106 | "score": 0, 107 | "subtitle": "", 108 | "title": "aljerraza.english.com", 109 | "type": "website", 110 | "uri": "http://aljerraza.english.com" 111 | }, 112 | { 113 | "type": "phrase", 114 | "title": "aljazeera net english live", 115 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Benglish\u002Blive", 116 | "score": 0 117 | }, 118 | { 119 | "type": "phrase", 120 | "title": "aljazeera net arabic", 121 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Barabic", 122 | "score": 0 123 | }, 124 | { 125 | "type": "phrase", 126 | "title": "aljerraza english live", 127 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish\u002Blive", 128 | "score": 0 129 | }, 130 | { 131 | "type": "phrase", 132 | "title": "aljerezza news", 133 | "uri": "https://duckduckgo.com/?q=aljerezza\u002Bnews", 134 | "score": 0 135 | }, 136 | { 137 | "type": "phrase", 138 | "title": "aljona savchenko", 139 | "uri": "https://duckduckgo.com/?q=aljona\u002Bsavchenko", 140 | "score": 0 141 | }, 142 | { 143 | "type": "phrase", 144 | "title": "alj discussion forum", 145 | "uri": "https://duckduckgo.com/?q=alj\u002Bdiscussion\u002Bforum", 146 | "score": 0 147 | } 148 | ], 149 | "localSuggestions": [ 150 | { 151 | "type": "historyEntry", 152 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 153 | "subtitle": "", 154 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 155 | "score": 307201 156 | } 157 | ] 158 | }, 159 | "$schema": "./search-suggestion-test-scenario-schema.json" 160 | } 161 | -------------------------------------------------------------------------------- /suggestions/history-nav-link-dont-dedupe.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "desktop", 3 | "description": "History url matches nav link, should not dedupe nav link", 4 | "input": { 5 | "query": "alj", 6 | "tabIdInitiatingSearch": "f49f851c-4627-4154-b26d-3d544b2658e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "aljerraza english" 10 | }, 11 | { 12 | "phrase": "aljazeera.com", 13 | "isNav": true 14 | }, 15 | { 16 | "phrase": "aljamain sterling" 17 | }, 18 | { 19 | "phrase": "aljerraza.english.com", 20 | "isNav": true 21 | }, 22 | { 23 | "phrase": "aljazeera net english live" 24 | }, 25 | { 26 | "phrase": "aljazeera net arabic" 27 | }, 28 | { 29 | "phrase": "aljerraza english live" 30 | }, 31 | { 32 | "phrase": "aljerezza news" 33 | }, 34 | { 35 | "phrase": "aljona savchenko" 36 | }, 37 | { 38 | "phrase": "alj discussion forum" 39 | } 40 | ], 41 | "bookmarks": [], 42 | "history": [ 43 | { 44 | "uri": "https://duckduckgo.com/", 45 | "title": "DuckDuckGo - Protection. Privacy. Peace of mind.", 46 | "visitCount": 1 47 | }, 48 | { 49 | "uri": "https://www.aljazeera.com/", 50 | "title": "Breaking News, World News and Video from Al Jazeera", 51 | "visitCount": 3 52 | }, 53 | { 54 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 55 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 56 | "visitCount": 1 57 | } 58 | ], 59 | "pinnedTabs": [], 60 | "windows": [ 61 | { 62 | "windowId": "a3be9d7e-f27c-4e59-8d04-cba6805f8fcf", 63 | "type": "fullyFeatured", 64 | "tabs": [ 65 | { 66 | "tabId": "f49f851c-4627-4154-b26d-3d544b2658e2", 67 | "uri": "duck://newtab", 68 | "title": "New Tab" 69 | } 70 | ] 71 | } 72 | ], 73 | "ignoredUris": [] 74 | }, 75 | "expectations": { 76 | "topHits": [ 77 | { 78 | "type": "historyEntry", 79 | "title": "Breaking News, World News and Video from Al Jazeera", 80 | "subtitle": "aljazeera.com", 81 | "uri": "https://www.aljazeera.com/", 82 | "score": 2355203 83 | }, 84 | { 85 | "score": 0, 86 | "subtitle": "", 87 | "title": "aljazeera.com", 88 | "type": "website", 89 | "uri": "http://aljazeera.com" 90 | } 91 | ], 92 | "searchSuggestions": [ 93 | { 94 | "type": "phrase", 95 | "title": "aljerraza english", 96 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish", 97 | "score": 0 98 | }, 99 | { 100 | "type": "phrase", 101 | "title": "aljamain sterling", 102 | "uri": "https://duckduckgo.com/?q=aljamain\u002Bsterling", 103 | "score": 0 104 | }, 105 | { 106 | "score": 0, 107 | "subtitle": "", 108 | "title": "aljerraza.english.com", 109 | "type": "website", 110 | "uri": "http://aljerraza.english.com" 111 | }, 112 | { 113 | "type": "phrase", 114 | "title": "aljazeera net english live", 115 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Benglish\u002Blive", 116 | "score": 0 117 | }, 118 | { 119 | "type": "phrase", 120 | "title": "aljazeera net arabic", 121 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Barabic", 122 | "score": 0 123 | }, 124 | { 125 | "type": "phrase", 126 | "title": "aljerraza english live", 127 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish\u002Blive", 128 | "score": 0 129 | }, 130 | { 131 | "type": "phrase", 132 | "title": "aljerezza news", 133 | "uri": "https://duckduckgo.com/?q=aljerezza\u002Bnews", 134 | "score": 0 135 | }, 136 | { 137 | "type": "phrase", 138 | "title": "aljona savchenko", 139 | "uri": "https://duckduckgo.com/?q=aljona\u002Bsavchenko", 140 | "score": 0 141 | }, 142 | { 143 | "type": "phrase", 144 | "title": "alj discussion forum", 145 | "uri": "https://duckduckgo.com/?q=alj\u002Bdiscussion\u002Bforum", 146 | "score": 0 147 | } 148 | ], 149 | "localSuggestions": [ 150 | { 151 | "type": "historyEntry", 152 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 153 | "subtitle": "aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 154 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 155 | "score": 307201 156 | } 157 | ] 158 | }, 159 | "$schema": "./search-suggestion-test-scenario-schema.json" 160 | } 161 | -------------------------------------------------------------------------------- /suggestions/history-removed-deduped-open-tabs-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "mobile", 3 | "description": "History url matches two opened tabs should dedupe and remove the open tabs when requested from one of the matching tabs", 4 | "input": { 5 | "query": "alj", 6 | "tabIdInitiatingSearch": "f49f851c-4627-4154-b26d-3d544b2658e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "aljerraza english" 10 | }, 11 | { 12 | "phrase": "www.aljazeera.com", 13 | "isNav": true 14 | }, 15 | { 16 | "phrase": "aljamain sterling" 17 | }, 18 | { 19 | "phrase": "aljerraza.english.com", 20 | "isNav": true 21 | }, 22 | { 23 | "phrase": "aljazeera net english live" 24 | }, 25 | { 26 | "phrase": "aljazeera net arabic" 27 | }, 28 | { 29 | "phrase": "aljerraza english live" 30 | }, 31 | { 32 | "phrase": "aljerezza news" 33 | }, 34 | { 35 | "phrase": "aljona savchenko" 36 | }, 37 | { 38 | "phrase": "alj discussion forum" 39 | } 40 | ], 41 | "bookmarks": [], 42 | "history": [ 43 | { 44 | "uri": "https://duckduckgo.com/", 45 | "title": "DuckDuckGo - Protection. Privacy. Peace of mind.", 46 | "visitCount": 1 47 | }, 48 | { 49 | "uri": "https://www.aljazeera.com/", 50 | "title": "Breaking News, World News and Video from Al Jazeera", 51 | "visitCount": 3 52 | }, 53 | { 54 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 55 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 56 | "visitCount": 1 57 | } 58 | ], 59 | "pinnedTabs": [], 60 | "windows": [ 61 | { 62 | "windowId": "a3be9d7e-f27c-4e59-8d04-cba6805f8fcf", 63 | "type": "fullyFeatured", 64 | "tabs": [ 65 | { 66 | "tabId": "00206920-e484-4e57-b8a9-38796f9ba4d2", 67 | "uri": "https://www.aljazeera.com/", 68 | "title": "Breaking News, World News and Video from Al Jazeera" 69 | }, 70 | { 71 | "tabId": "f49f851c-4627-4154-b26d-3d544b2658e2", 72 | "uri": "https://www.aljazeera.com/", 73 | "title": "Breaking News, World News and Video from Al Jazeera" 74 | } 75 | ] 76 | } 77 | ], 78 | "ignoredUris": [] 79 | }, 80 | "expectations": { 81 | "topHits": [ 82 | { 83 | "type": "historyEntry", 84 | "title": "Breaking News, World News and Video from Al Jazeera", 85 | "subtitle": "", 86 | "uri": "https://www.aljazeera.com/", 87 | "score": 2355203 88 | }, 89 | { 90 | "score" : 0, 91 | "subtitle" : "", 92 | "title" : "www.aljazeera.com", 93 | "type" : "website", 94 | "uri" : "http:\/\/www.aljazeera.com" 95 | } 96 | ], 97 | "searchSuggestions": [ 98 | { 99 | "type": "phrase", 100 | "title": "aljerraza english", 101 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish", 102 | "score": 0 103 | }, 104 | { 105 | "type": "phrase", 106 | "title": "aljamain sterling", 107 | "uri": "https://duckduckgo.com/?q=aljamain\u002Bsterling", 108 | "score": 0 109 | }, 110 | { 111 | "score" : 0, 112 | "subtitle" : "", 113 | "title" : "aljerraza.english.com", 114 | "type" : "website", 115 | "uri" : "http:\/\/aljerraza.english.com" 116 | }, 117 | { 118 | "type": "phrase", 119 | "title": "aljazeera net english live", 120 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Benglish\u002Blive", 121 | "score": 0 122 | }, 123 | { 124 | "type": "phrase", 125 | "title": "aljazeera net arabic", 126 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Barabic", 127 | "score": 0 128 | }, 129 | { 130 | "type": "phrase", 131 | "title": "aljerraza english live", 132 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish\u002Blive", 133 | "score": 0 134 | }, 135 | { 136 | "type": "phrase", 137 | "title": "aljerezza news", 138 | "uri": "https://duckduckgo.com/?q=aljerezza\u002Bnews", 139 | "score": 0 140 | }, 141 | { 142 | "type": "phrase", 143 | "title": "aljona savchenko", 144 | "uri": "https://duckduckgo.com/?q=aljona\u002Bsavchenko", 145 | "score": 0 146 | }, 147 | { 148 | "type": "phrase", 149 | "title": "alj discussion forum", 150 | "uri": "https://duckduckgo.com/?q=alj\u002Bdiscussion\u002Bforum", 151 | "score": 0 152 | } 153 | ], 154 | "localSuggestions": [ 155 | { 156 | "type": "historyEntry", 157 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 158 | "subtitle": "", 159 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 160 | "score": 307201 161 | } 162 | ] 163 | }, 164 | "$schema": "./search-suggestion-test-scenario-schema.json" 165 | } 166 | -------------------------------------------------------------------------------- /suggestions/history-removed-deduped-open-tabs.json: -------------------------------------------------------------------------------- 1 | { 2 | "platform": "desktop", 3 | "description": "History url matches two opened tabs should dedupe and remove the open tabs when requested from one of the matching tabs", 4 | "input": { 5 | "query": "alj", 6 | "tabIdInitiatingSearch": "f49f851c-4627-4154-b26d-3d544b2658e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "aljerraza english" 10 | }, 11 | { 12 | "phrase": "aljamain sterling" 13 | }, 14 | { 15 | "phrase": "aljazeera net english live" 16 | }, 17 | { 18 | "phrase": "aljazeera net arabic" 19 | }, 20 | { 21 | "phrase": "aljerraza english live" 22 | }, 23 | { 24 | "phrase": "aljerezza news" 25 | }, 26 | { 27 | "phrase": "aljona savchenko" 28 | }, 29 | { 30 | "phrase": "alj discussion forum" 31 | } 32 | ], 33 | "bookmarks": [], 34 | "history": [ 35 | { 36 | "uri": "https://duckduckgo.com/", 37 | "title": "DuckDuckGo - Protection. Privacy. Peace of mind.", 38 | "visitCount": 1 39 | }, 40 | { 41 | "uri": "https://www.aljazeera.com/", 42 | "title": "Breaking News, World News and Video from Al Jazeera", 43 | "visitCount": 3 44 | }, 45 | { 46 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 47 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 48 | "visitCount": 1 49 | } 50 | ], 51 | "pinnedTabs": [], 52 | "windows": [ 53 | { 54 | "windowId": "a3be9d7e-f27c-4e59-8d04-cba6805f8fcf", 55 | "type": "fullyFeatured", 56 | "tabs": [ 57 | { 58 | "tabId": "00206920-e484-4e57-b8a9-38796f9ba4d2", 59 | "uri": "https://www.aljazeera.com/", 60 | "title": "Breaking News, World News and Video from Al Jazeera" 61 | }, 62 | { 63 | "tabId": "f49f851c-4627-4154-b26d-3d544b2658e2", 64 | "uri": "https://www.aljazeera.com/", 65 | "title": "Breaking News, World News and Video from Al Jazeera" 66 | } 67 | ] 68 | } 69 | ], 70 | "ignoredUris": [] 71 | }, 72 | "expectations": { 73 | "topHits": [ 74 | { 75 | "type": "historyEntry", 76 | "title": "Breaking News, World News and Video from Al Jazeera", 77 | "subtitle": "aljazeera.com", 78 | "uri": "https://www.aljazeera.com/", 79 | "score": 2355203 80 | } 81 | ], 82 | "searchSuggestions": [ 83 | { 84 | "type": "phrase", 85 | "title": "aljerraza english", 86 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish", 87 | "score": 0 88 | }, 89 | { 90 | "type": "phrase", 91 | "title": "aljamain sterling", 92 | "uri": "https://duckduckgo.com/?q=aljamain\u002Bsterling", 93 | "score": 0 94 | }, 95 | { 96 | "type": "phrase", 97 | "title": "aljazeera net english live", 98 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Benglish\u002Blive", 99 | "score": 0 100 | }, 101 | { 102 | "type": "phrase", 103 | "title": "aljazeera net arabic", 104 | "uri": "https://duckduckgo.com/?q=aljazeera\u002Bnet\u002Barabic", 105 | "score": 0 106 | }, 107 | { 108 | "type": "phrase", 109 | "title": "aljerraza english live", 110 | "uri": "https://duckduckgo.com/?q=aljerraza\u002Benglish\u002Blive", 111 | "score": 0 112 | }, 113 | { 114 | "type": "phrase", 115 | "title": "aljerezza news", 116 | "uri": "https://duckduckgo.com/?q=aljerezza\u002Bnews", 117 | "score": 0 118 | }, 119 | { 120 | "type": "phrase", 121 | "title": "aljona savchenko", 122 | "uri": "https://duckduckgo.com/?q=aljona\u002Bsavchenko", 123 | "score": 0 124 | }, 125 | { 126 | "type": "phrase", 127 | "title": "alj discussion forum", 128 | "uri": "https://duckduckgo.com/?q=alj\u002Bdiscussion\u002Bforum", 129 | "score": 0 130 | } 131 | ], 132 | "localSuggestions": [ 133 | { 134 | "type": "historyEntry", 135 | "title": "Two-thirds of people Israel killed in Gaza strikes were women and children | Israel-Palestine conflict News | Al Jazeera", 136 | "subtitle": "aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 137 | "uri": "https://www.aljazeera.com/news/2025/3/19/two-thirds-of-people-israel-killed-in-strikes-were-women-and-children", 138 | "score": 307201 139 | } 140 | ] 141 | }, 142 | "$schema": "./search-suggestion-test-scenario-schema.json" 143 | } 144 | -------------------------------------------------------------------------------- /suggestions/maximum-suggestions-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Maximum Suggestions Enforcement test: the algorithm should adhere to the maximum suggestion counts (topHits = 2, total = 12)", 4 | "input": { 5 | "query": "a", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "amazon" 10 | }, 11 | { 12 | "phrase": "apple" 13 | }, 14 | { 15 | "phrase": "asos" 16 | }, 17 | { 18 | "phrase": "aol" 19 | }, 20 | { 21 | "phrase": "airbnb" 22 | }, 23 | { 24 | "phrase": "att" 25 | }, 26 | { 27 | "phrase": "autozone" 28 | }, 29 | { 30 | "phrase": "american airlines" 31 | }, 32 | { 33 | "phrase": "amazon prime" 34 | }, 35 | { 36 | "phrase": "apple store" 37 | }, 38 | { 39 | "phrase": "apartments.com" 40 | }, 41 | { 42 | "phrase": "alaska airlines" 43 | }, 44 | { 45 | "phrase": "american express" 46 | }, 47 | { 48 | "phrase": "adidas" 49 | }, 50 | { 51 | "phrase": "app store" 52 | } 53 | ], 54 | "bookmarks": [ 55 | { 56 | "uri": "https://www.amazon.com/", 57 | "title": "Amazon.com: Online Shopping", 58 | "isFavorite": true 59 | }, 60 | { 61 | "uri": "https://www.apple.com/", 62 | "title": "Apple", 63 | "isFavorite": false 64 | } 65 | ], 66 | "history": [ 67 | { 68 | "uri": "https://www.asos.com/", 69 | "title": "ASOS | Online Shopping for the Latest Clothes & Fashion", 70 | "visitCount": 15 71 | }, 72 | { 73 | "uri": "https://www.airbnb.com/", 74 | "title": "Airbnb: Vacation Rentals, Cabins, Beach Houses & More", 75 | "visitCount": 10 76 | }, 77 | { 78 | "uri": "https://www.att.com/", 79 | "title": "AT&T Official Site", 80 | "visitCount": 8 81 | }, 82 | { 83 | "uri": "https://www.autozone.com/", 84 | "title": "AutoZone - Auto Parts, Accessories, and Advice", 85 | "visitCount": 7 86 | }, 87 | { 88 | "uri": "https://www.adidas.com/", 89 | "title": "adidas Official Website", 90 | "visitCount": 6 91 | } 92 | ], 93 | "pinnedTabs": [], 94 | "windows": [ 95 | { 96 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 97 | "type": "fullyFeatured", 98 | "tabs": [ 99 | { 100 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 101 | "uri": "https://www.aol.com/", 102 | "title": "AOL - News, Email and Search" 103 | }, 104 | { 105 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 106 | "uri": "https://www.aa.com/", 107 | "title": "American Airlines - Airline Tickets and Cheap Flights" 108 | }, 109 | { 110 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 111 | "uri": "duck://newtab", 112 | "title": "New Tab" 113 | } 114 | ] 115 | } 116 | ], 117 | "ignoredUris": [] 118 | }, 119 | "expectations": { 120 | "topHits": [ 121 | { 122 | "type": "historyEntry", 123 | "title": "ASOS | Online Shopping for the Latest Clothes & Fashion", 124 | "subtitle": "asos.com", 125 | "uri": "https://www.asos.com/", 126 | "score": 2355215 127 | }, 128 | { 129 | "type": "historyEntry", 130 | "title": "Airbnb: Vacation Rentals, Cabins, Beach Houses & More", 131 | "subtitle": "airbnb.com", 132 | "uri": "https://www.airbnb.com/", 133 | "score": 2355210 134 | } 135 | ], 136 | "searchSuggestions": [ 137 | { 138 | "type": "phrase", 139 | "title": "amazon", 140 | "uri": "https://duckduckgo.com/?q=amazon", 141 | "score": 0 142 | }, 143 | { 144 | "type": "phrase", 145 | "title": "apple", 146 | "uri": "https://duckduckgo.com/?q=apple", 147 | "score": 0 148 | }, 149 | { 150 | "type": "phrase", 151 | "title": "asos", 152 | "uri": "https://duckduckgo.com/?q=asos", 153 | "score": 0 154 | }, 155 | { 156 | "type": "phrase", 157 | "title": "aol", 158 | "uri": "https://duckduckgo.com/?q=aol", 159 | "score": 0 160 | }, 161 | { 162 | "type": "phrase", 163 | "title": "airbnb", 164 | "uri": "https://duckduckgo.com/?q=airbnb", 165 | "score": 0 166 | }, 167 | { 168 | "type": "phrase", 169 | "title": "att", 170 | "uri": "https://duckduckgo.com/?q=att", 171 | "score": 0 172 | }, 173 | { 174 | "type": "phrase", 175 | "title": "autozone", 176 | "uri": "https://duckduckgo.com/?q=autozone", 177 | "score": 0 178 | }, 179 | { 180 | "type": "phrase", 181 | "title": "american airlines", 182 | "uri": "https://duckduckgo.com/?q=american+airlines", 183 | "score": 0 184 | }, 185 | { 186 | "type": "phrase", 187 | "title": "amazon prime", 188 | "uri": "https://duckduckgo.com/?q=amazon+prime", 189 | "score": 0 190 | }, 191 | { 192 | "type": "phrase", 193 | "title": "apple store", 194 | "uri": "https://duckduckgo.com/?q=apple+store", 195 | "score": 0 196 | } 197 | ], 198 | "localSuggestions": [] 199 | }, 200 | "platform": "desktop" 201 | } -------------------------------------------------------------------------------- /suggestions/maximum-suggestions-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Maximum Suggestions Enforcement test for mobile: the algorithm should adhere to the maximum suggestion counts (topHits = 2, total = 12)", 4 | "input": { 5 | "query": "a", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "amazon" 10 | }, 11 | { 12 | "phrase": "apple" 13 | }, 14 | { 15 | "phrase": "asos" 16 | }, 17 | { 18 | "phrase": "aol" 19 | }, 20 | { 21 | "phrase": "airbnb" 22 | }, 23 | { 24 | "phrase": "att" 25 | }, 26 | { 27 | "phrase": "autozone" 28 | }, 29 | { 30 | "phrase": "american airlines" 31 | }, 32 | { 33 | "phrase": "amazon prime" 34 | }, 35 | { 36 | "phrase": "apple store" 37 | }, 38 | { 39 | "phrase": "apartments.com" 40 | }, 41 | { 42 | "phrase": "alaska airlines" 43 | }, 44 | { 45 | "phrase": "american express" 46 | }, 47 | { 48 | "phrase": "adidas" 49 | }, 50 | { 51 | "phrase": "app store" 52 | } 53 | ], 54 | "bookmarks": [ 55 | { 56 | "uri": "https://www.amazon.com/", 57 | "title": "Amazon.com: Online Shopping", 58 | "isFavorite": true 59 | }, 60 | { 61 | "uri": "https://www.apple.com/", 62 | "title": "Apple", 63 | "isFavorite": false 64 | } 65 | ], 66 | "history": [ 67 | { 68 | "uri": "https://www.asos.com/", 69 | "title": "ASOS | Online Shopping for the Latest Clothes & Fashion", 70 | "visitCount": 15 71 | }, 72 | { 73 | "uri": "https://www.airbnb.com/", 74 | "title": "Airbnb: Vacation Rentals, Cabins, Beach Houses & More", 75 | "visitCount": 10 76 | }, 77 | { 78 | "uri": "https://www.att.com/", 79 | "title": "AT&T Official Site", 80 | "visitCount": 8 81 | }, 82 | { 83 | "uri": "https://www.autozone.com/", 84 | "title": "AutoZone - Auto Parts, Accessories, and Advice", 85 | "visitCount": 7 86 | }, 87 | { 88 | "uri": "https://www.adidas.com/", 89 | "title": "adidas Official Website", 90 | "visitCount": 6 91 | } 92 | ], 93 | "pinnedTabs": [], 94 | "windows": [ 95 | { 96 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 97 | "type": "fullyFeatured", 98 | "tabs": [ 99 | { 100 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 101 | "uri": "https://www.aol.com/", 102 | "title": "AOL - News, Email and Search" 103 | }, 104 | { 105 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 106 | "uri": "https://www.aa.com/", 107 | "title": "American Airlines - Airline Tickets and Cheap Flights" 108 | }, 109 | { 110 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 111 | "uri": "duck://newtab", 112 | "title": "New Tab" 113 | } 114 | ] 115 | } 116 | ], 117 | "ignoredUris": [] 118 | }, 119 | "expectations": { 120 | "topHits": [ 121 | { 122 | "type": "historyEntry", 123 | "title": "ASOS | Online Shopping for the Latest Clothes & Fashion", 124 | "subtitle": "", 125 | "uri": "https://www.asos.com/", 126 | "score": 2355215 127 | }, 128 | { 129 | "type": "historyEntry", 130 | "title": "Airbnb: Vacation Rentals, Cabins, Beach Houses & More", 131 | "subtitle": "", 132 | "uri": "https://www.airbnb.com/", 133 | "score": 2355210 134 | } 135 | ], 136 | "searchSuggestions": [ 137 | { 138 | "type": "phrase", 139 | "title": "amazon", 140 | "uri": "https://duckduckgo.com/?q=amazon", 141 | "score": 0 142 | }, 143 | { 144 | "type": "phrase", 145 | "title": "apple", 146 | "uri": "https://duckduckgo.com/?q=apple", 147 | "score": 0 148 | }, 149 | { 150 | "type": "phrase", 151 | "title": "asos", 152 | "uri": "https://duckduckgo.com/?q=asos", 153 | "score": 0 154 | }, 155 | { 156 | "type": "phrase", 157 | "title": "aol", 158 | "uri": "https://duckduckgo.com/?q=aol", 159 | "score": 0 160 | }, 161 | { 162 | "type": "phrase", 163 | "title": "airbnb", 164 | "uri": "https://duckduckgo.com/?q=airbnb", 165 | "score": 0 166 | }, 167 | { 168 | "type": "phrase", 169 | "title": "att", 170 | "uri": "https://duckduckgo.com/?q=att", 171 | "score": 0 172 | }, 173 | { 174 | "type": "phrase", 175 | "title": "autozone", 176 | "uri": "https://duckduckgo.com/?q=autozone", 177 | "score": 0 178 | }, 179 | { 180 | "type": "phrase", 181 | "title": "american airlines", 182 | "uri": "https://duckduckgo.com/?q=american+airlines", 183 | "score": 0 184 | }, 185 | { 186 | "type": "phrase", 187 | "title": "amazon prime", 188 | "uri": "https://duckduckgo.com/?q=amazon+prime", 189 | "score": 0 190 | }, 191 | { 192 | "type": "phrase", 193 | "title": "apple store", 194 | "uri": "https://duckduckgo.com/?q=apple+store", 195 | "score": 0 196 | } 197 | ], 198 | "localSuggestions": [] 199 | }, 200 | "platform": "mobile" 201 | } 202 | -------------------------------------------------------------------------------- /suggestions/quality-based-deduplication-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Quality-based deduplication test: when there are duplicates with the same URL but different types, the one with the highest quality should be chosen (favorite > bookmark > history > tab)", 4 | "input": { 5 | "query": "duck", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "duckduckgo" 10 | }, 11 | { 12 | "phrase": "duck sauce" 13 | }, 14 | { 15 | "phrase": "duck hunt" 16 | } 17 | ], 18 | "bookmarks": [ 19 | { 20 | "uri": "https://www.duckduckgo.com/", 21 | "title": "DuckDuckGo — Privacy, simplified.", 22 | "isFavorite": true 23 | } 24 | ], 25 | "history": [ 26 | { 27 | "uri": "https://www.duckduckgo.com/", 28 | "title": "DuckDuckGo — Privacy, simplified.", 29 | "visitCount": 25 30 | }, 31 | { 32 | "uri": "https://duck.com/", 33 | "title": "DuckDuckGo", 34 | "visitCount": 12 35 | } 36 | ], 37 | "pinnedTabs": [], 38 | "windows": [ 39 | { 40 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 41 | "type": "fullyFeatured", 42 | "tabs": [ 43 | { 44 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 45 | "uri": "https://www.duckduckgo.com/", 46 | "title": "DuckDuckGo — Privacy, simplified." 47 | }, 48 | { 49 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 50 | "uri": "duck://newtab", 51 | "title": "New Tab" 52 | } 53 | ] 54 | } 55 | ], 56 | "ignoredUris": [] 57 | }, 58 | "expectations": { 59 | "topHits": [ 60 | { 61 | "type": "favorite", 62 | "title": "DuckDuckGo — Privacy, simplified.", 63 | "subtitle": "duckduckgo.com", 64 | "uri": "https://www.duckduckgo.com/", 65 | "score": 2355225 66 | }, 67 | { 68 | "type": "openTab", 69 | "title": "DuckDuckGo — Privacy, simplified.", 70 | "subtitle": "duckduckgo.com", 71 | "uri": "https://www.duckduckgo.com/", 72 | "score": 2355225, 73 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275" 74 | } 75 | ], 76 | "searchSuggestions": [ 77 | { 78 | "type": "phrase", 79 | "title": "duckduckgo", 80 | "uri": "https://duckduckgo.com/?q=duckduckgo", 81 | "score": 0 82 | }, 83 | { 84 | "type": "phrase", 85 | "title": "duck sauce", 86 | "uri": "https://duckduckgo.com/?q=duck+sauce", 87 | "score": 0 88 | }, 89 | { 90 | "type": "phrase", 91 | "title": "duck hunt", 92 | "uri": "https://duckduckgo.com/?q=duck+hunt", 93 | "score": 0 94 | } 95 | ], 96 | "localSuggestions": [ 97 | { 98 | "type": "historyEntry", 99 | "title": "DuckDuckGo", 100 | "subtitle": "duck.com", 101 | "uri": "https://duck.com/", 102 | "score": 2355212 103 | }, 104 | { 105 | "type": "internalPage", 106 | "title": "Settings → Duck Player", 107 | "subtitle": "DuckDuckGo", 108 | "uri": "duck://settings/duckplayer", 109 | "score": 102400 110 | }, 111 | { 112 | "type": "internalPage", 113 | "title": "Settings → DuckDuckGo on Other Platforms", 114 | "subtitle": "DuckDuckGo", 115 | "uri": "duck://settings/https://duckduckgo.com/app/devices%3Forigin=funnel_app_macos", 116 | "score": 102400 117 | } 118 | ] 119 | }, 120 | "platform": "desktop" 121 | } 122 | -------------------------------------------------------------------------------- /suggestions/quality-based-deduplication-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Quality-based deduplication test for mobile: when there are duplicates with the same URL but different types, the one with the highest quality should be chosen (favorite > bookmark > history > tab)", 4 | "input": { 5 | "query": "duck", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "duckduckgo" 10 | }, 11 | { 12 | "phrase": "duck sauce" 13 | }, 14 | { 15 | "phrase": "duck hunt" 16 | } 17 | ], 18 | "bookmarks": [ 19 | { 20 | "uri": "https://www.duckduckgo.com/", 21 | "title": "DuckDuckGo — Privacy, simplified.", 22 | "isFavorite": true 23 | } 24 | ], 25 | "history": [ 26 | { 27 | "uri": "https://www.duckduckgo.com/", 28 | "title": "DuckDuckGo — Privacy, simplified.", 29 | "visitCount": 25 30 | }, 31 | { 32 | "uri": "https://duck.com/", 33 | "title": "DuckDuckGo", 34 | "visitCount": 12 35 | } 36 | ], 37 | "pinnedTabs": [], 38 | "windows": [ 39 | { 40 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 41 | "type": "fullyFeatured", 42 | "tabs": [ 43 | { 44 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 45 | "uri": "https://www.duckduckgo.com/", 46 | "title": "DuckDuckGo — Privacy, simplified." 47 | }, 48 | { 49 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 50 | "uri": "duck://newtab", 51 | "title": "New Tab" 52 | } 53 | ] 54 | } 55 | ], 56 | "ignoredUris": [] 57 | }, 58 | "expectations": { 59 | "topHits": [ 60 | { 61 | "type": "favorite", 62 | "title": "DuckDuckGo — Privacy, simplified.", 63 | "subtitle": "", 64 | "uri": "https://www.duckduckgo.com/", 65 | "score": 2355225 66 | }, 67 | { 68 | "type": "openTab", 69 | "title": "DuckDuckGo — Privacy, simplified.", 70 | "subtitle": "", 71 | "uri": "https://www.duckduckgo.com/", 72 | "score": 2355225, 73 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275" 74 | } 75 | ], 76 | "searchSuggestions": [ 77 | { 78 | "type": "phrase", 79 | "title": "duckduckgo", 80 | "uri": "https://duckduckgo.com/?q=duckduckgo", 81 | "score": 0 82 | }, 83 | { 84 | "type": "phrase", 85 | "title": "duck sauce", 86 | "uri": "https://duckduckgo.com/?q=duck+sauce", 87 | "score": 0 88 | }, 89 | { 90 | "type": "phrase", 91 | "title": "duck hunt", 92 | "uri": "https://duckduckgo.com/?q=duck+hunt", 93 | "score": 0 94 | } 95 | ], 96 | "localSuggestions": [ 97 | { 98 | "type": "historyEntry", 99 | "title": "DuckDuckGo", 100 | "subtitle": "", 101 | "uri": "https://duck.com/", 102 | "score": 2355212 103 | } 104 | ] 105 | }, 106 | "platform": "mobile" 107 | } 108 | -------------------------------------------------------------------------------- /suggestions/special-pages-bookmarks-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test: duck://bookmarks should be suggested at the bottom of the list as an Open Tab suggestion when entering 'boo'", 4 | "input": { 5 | "query": "boo", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "booktopia" 10 | }, 11 | { 12 | "phrase": "booking.com", 13 | "isNav": true 14 | }, 15 | { 16 | "phrase": "boohoo" 17 | }, 18 | { 19 | "phrase": "books" 20 | }, 21 | { 22 | "phrase": "bookmyshow" 23 | }, 24 | { 25 | "phrase": "boohooman" 26 | }, 27 | { 28 | "phrase": "bookings" 29 | }, 30 | { 31 | "phrase": "bootstrap" 32 | } 33 | ], 34 | "bookmarks": [ 35 | { 36 | "uri": "https://www.bookdepository.com/", 37 | "title": "Book Depository - Free Delivery Worldwide", 38 | "isFavorite": true 39 | } 40 | ], 41 | "history": [ 42 | { 43 | "uri": "https://duckduckgo.com/", 44 | "title": "DuckDuckGo - Your protection, our priority.", 45 | "visitCount": 45 46 | }, 47 | { 48 | "uri": "https://www.booking.com/", 49 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 50 | "visitCount": 7 51 | }, 52 | { 53 | "uri": "https://app.asana.com/0/home/1206786699935999", 54 | "title": "Home - Asana", 55 | "visitCount": 6 56 | }, 57 | { 58 | "uri": "https://app.asana.com/", 59 | "title": "Asana", 60 | "visitCount": 5 61 | } 62 | ], 63 | "pinnedTabs": [], 64 | "windows": [ 65 | { 66 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 67 | "type": "fullyFeatured", 68 | "tabs": [ 69 | { 70 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 71 | "uri": "https://app.asana.com/0/home/1206786699935999", 72 | "title": "Home - Asana" 73 | }, 74 | { 75 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a", 76 | "uri": "duck://bookmarks", 77 | "title": "Bookmarks" 78 | }, 79 | { 80 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 81 | "uri": "duck://newtab", 82 | "title": "New Tab" 83 | } 84 | ] 85 | } 86 | ], 87 | "ignoredUris": [ 88 | "app.asana.com" 89 | ] 90 | }, 91 | "expectations": { 92 | "topHits": [ 93 | { 94 | "type": "historyEntry", 95 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 96 | "subtitle": "booking.com", 97 | "uri": "https://www.booking.com/", 98 | "score": 2355207 99 | }, 100 | { 101 | "type": "favorite", 102 | "title": "Book Depository - Free Delivery Worldwide", 103 | "subtitle": "bookdepository.com", 104 | "uri": "https://www.bookdepository.com/", 105 | "score": 2355200 106 | } 107 | ], 108 | "searchSuggestions": [ 109 | { 110 | "type": "phrase", 111 | "title": "booktopia", 112 | "uri": "https://duckduckgo.com/?q=booktopia", 113 | "score": 0 114 | }, 115 | { 116 | "type": "website", 117 | "title": "booking.com", 118 | "subtitle": "", 119 | "uri": "http://booking.com", 120 | "score": 0 121 | }, 122 | { 123 | "type": "phrase", 124 | "title": "boohoo", 125 | "uri": "https://duckduckgo.com/?q=boohoo", 126 | "score": 0 127 | }, 128 | { 129 | "type": "phrase", 130 | "title": "books", 131 | "uri": "https://duckduckgo.com/?q=books", 132 | "score": 0 133 | }, 134 | { 135 | "type": "phrase", 136 | "title": "bookmyshow", 137 | "uri": "https://duckduckgo.com/?q=bookmyshow", 138 | "score": 0 139 | }, 140 | { 141 | "type": "phrase", 142 | "title": "boohooman", 143 | "uri": "https://duckduckgo.com/?q=boohooman", 144 | "score": 0 145 | }, 146 | { 147 | "type": "phrase", 148 | "title": "bookings", 149 | "uri": "https://duckduckgo.com/?q=bookings", 150 | "score": 0 151 | }, 152 | { 153 | "type": "phrase", 154 | "title": "bootstrap", 155 | "uri": "https://duckduckgo.com/?q=bootstrap", 156 | "score": 0 157 | } 158 | ], 159 | "localSuggestions": [ 160 | { 161 | "type": "openTab", 162 | "title": "Bookmarks", 163 | "subtitle": "DuckDuckGo", 164 | "uri": "duck://bookmarks", 165 | "score": 2355200, 166 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a" 167 | } 168 | ] 169 | }, 170 | "platform": "desktop" 171 | } 172 | -------------------------------------------------------------------------------- /suggestions/special-pages-bookmarks-no-tab-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test: duck://bookmarks should be suggested as a local suggestion when entering 'boo' even when no tab is open for it", 4 | "input": { 5 | "query": "boo", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "booktopia" 10 | }, 11 | { 12 | "phrase": "booking.com" 13 | }, 14 | { 15 | "phrase": "boohoo" 16 | }, 17 | { 18 | "phrase": "books" 19 | }, 20 | { 21 | "phrase": "bookmyshow" 22 | }, 23 | { 24 | "phrase": "boohooman" 25 | }, 26 | { 27 | "phrase": "bookings" 28 | }, 29 | { 30 | "phrase": "bootstrap" 31 | } 32 | ], 33 | "bookmarks": [ 34 | { 35 | "uri": "https://www.bookdepository.com/", 36 | "title": "Book Depository - Free Delivery Worldwide", 37 | "isFavorite": true 38 | } 39 | ], 40 | "history": [ 41 | { 42 | "uri": "https://duckduckgo.com/", 43 | "title": "DuckDuckGo - Your protection, our priority.", 44 | "visitCount": 45 45 | }, 46 | { 47 | "uri": "https://www.booking.com/", 48 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 49 | "visitCount": 7 50 | }, 51 | { 52 | "uri": "https://app.asana.com/0/home/1206786699935999", 53 | "title": "Home - Asana", 54 | "visitCount": 6 55 | }, 56 | { 57 | "uri": "https://app.asana.com/", 58 | "title": "Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/home/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 75 | "uri": "duck://newtab", 76 | "title": "New Tab" 77 | } 78 | ] 79 | } 80 | ], 81 | "ignoredUris": [ 82 | "app.asana.com" 83 | ] 84 | }, 85 | "expectations": { 86 | "topHits": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 90 | "subtitle": "booking.com", 91 | "uri": "https://www.booking.com/", 92 | "score": 2355207 93 | }, 94 | { 95 | "type": "favorite", 96 | "title": "Book Depository - Free Delivery Worldwide", 97 | "subtitle": "bookdepository.com", 98 | "uri": "https://www.bookdepository.com/", 99 | "score": 2355200 100 | } 101 | ], 102 | "searchSuggestions": [ 103 | { 104 | "type": "phrase", 105 | "title": "booktopia", 106 | "uri": "https://duckduckgo.com/?q=booktopia", 107 | "score": 0 108 | }, 109 | { 110 | "type": "phrase", 111 | "title": "booking.com", 112 | "uri": "https://duckduckgo.com/?q=booking.com", 113 | "score": 0 114 | }, 115 | { 116 | "type": "phrase", 117 | "title": "boohoo", 118 | "uri": "https://duckduckgo.com/?q=boohoo", 119 | "score": 0 120 | }, 121 | { 122 | "type": "phrase", 123 | "title": "books", 124 | "uri": "https://duckduckgo.com/?q=books", 125 | "score": 0 126 | }, 127 | { 128 | "type": "phrase", 129 | "title": "bookmyshow", 130 | "uri": "https://duckduckgo.com/?q=bookmyshow", 131 | "score": 0 132 | }, 133 | { 134 | "type": "phrase", 135 | "title": "boohooman", 136 | "uri": "https://duckduckgo.com/?q=boohooman", 137 | "score": 0 138 | }, 139 | { 140 | "type": "phrase", 141 | "title": "bookings", 142 | "uri": "https://duckduckgo.com/?q=bookings", 143 | "score": 0 144 | }, 145 | { 146 | "type": "phrase", 147 | "title": "bootstrap", 148 | "uri": "https://duckduckgo.com/?q=bootstrap", 149 | "score": 0 150 | } 151 | ], 152 | "localSuggestions": [ 153 | { 154 | "type": "internalPage", 155 | "title": "Bookmarks", 156 | "subtitle": "DuckDuckGo", 157 | "uri": "duck://bookmarks", 158 | "score": 2355200 159 | } 160 | ] 161 | }, 162 | "platform": "desktop" 163 | } -------------------------------------------------------------------------------- /suggestions/special-pages-bookmarks-no-tab-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test (mobile): duck://bookmarks should be suggested as a local suggestion when entering 'boo' even when no tab is open for it", 4 | "input": { 5 | "query": "boo", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "booktopia" 10 | }, 11 | { 12 | "phrase": "booking.com" 13 | }, 14 | { 15 | "phrase": "boohoo" 16 | }, 17 | { 18 | "phrase": "books" 19 | }, 20 | { 21 | "phrase": "bookmyshow" 22 | }, 23 | { 24 | "phrase": "boohooman" 25 | }, 26 | { 27 | "phrase": "bookings" 28 | }, 29 | { 30 | "phrase": "bootstrap" 31 | } 32 | ], 33 | "bookmarks": [ 34 | { 35 | "uri": "https://www.bookdepository.com/", 36 | "title": "Book Depository - Free Delivery Worldwide", 37 | "isFavorite": true 38 | } 39 | ], 40 | "history": [ 41 | { 42 | "uri": "https://duckduckgo.com/", 43 | "title": "DuckDuckGo - Your protection, our priority.", 44 | "visitCount": 45 45 | }, 46 | { 47 | "uri": "https://www.booking.com/", 48 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 49 | "visitCount": 7 50 | }, 51 | { 52 | "uri": "https://app.asana.com/0/home/1206786699935999", 53 | "title": "Home - Asana", 54 | "visitCount": 6 55 | }, 56 | { 57 | "uri": "https://app.asana.com/", 58 | "title": "Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/home/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 75 | "uri": "duck://newtab", 76 | "title": "New Tab" 77 | } 78 | ] 79 | } 80 | ], 81 | "ignoredUris": [ 82 | "app.asana.com" 83 | ] 84 | }, 85 | "expectations": { 86 | "topHits": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "Booking.com | Official site | The best hotels, flights & car rentals", 90 | "subtitle": "", 91 | "uri": "https://www.booking.com/", 92 | "score": 2355207 93 | }, 94 | { 95 | "type": "favorite", 96 | "title": "Book Depository - Free Delivery Worldwide", 97 | "subtitle": "", 98 | "uri": "https://www.bookdepository.com/", 99 | "score": 2355200 100 | } 101 | ], 102 | "searchSuggestions": [ 103 | { 104 | "type": "phrase", 105 | "title": "booktopia", 106 | "uri": "https://duckduckgo.com/?q=booktopia", 107 | "score": 0 108 | }, 109 | { 110 | "type": "phrase", 111 | "title": "booking.com", 112 | "uri": "https://duckduckgo.com/?q=booking.com", 113 | "score": 0 114 | }, 115 | { 116 | "type": "phrase", 117 | "title": "boohoo", 118 | "uri": "https://duckduckgo.com/?q=boohoo", 119 | "score": 0 120 | }, 121 | { 122 | "type": "phrase", 123 | "title": "books", 124 | "uri": "https://duckduckgo.com/?q=books", 125 | "score": 0 126 | }, 127 | { 128 | "type": "phrase", 129 | "title": "bookmyshow", 130 | "uri": "https://duckduckgo.com/?q=bookmyshow", 131 | "score": 0 132 | }, 133 | { 134 | "type": "phrase", 135 | "title": "boohooman", 136 | "uri": "https://duckduckgo.com/?q=boohooman", 137 | "score": 0 138 | }, 139 | { 140 | "type": "phrase", 141 | "title": "bookings", 142 | "uri": "https://duckduckgo.com/?q=bookings", 143 | "score": 0 144 | }, 145 | { 146 | "type": "phrase", 147 | "title": "bootstrap", 148 | "uri": "https://duckduckgo.com/?q=bootstrap", 149 | "score": 0 150 | } 151 | ], 152 | "localSuggestions": [ 153 | 154 | ] 155 | }, 156 | "platform": "mobile" 157 | } 158 | -------------------------------------------------------------------------------- /suggestions/special-pages-settings-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test: duck://settings should be suggested at the bottom of the list as an Open Tab suggestion when entering 'set'", 4 | "input": { 5 | "query": "set", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "setlist fm" 10 | }, 11 | { 12 | "phrase": "settings" 13 | }, 14 | { 15 | "phrase": "settings app" 16 | }, 17 | { 18 | "phrase": "setup" 19 | }, 20 | { 21 | "phrase": "set theory" 22 | }, 23 | { 24 | "phrase": "setup wizard" 25 | }, 26 | { 27 | "phrase": "seton hall university" 28 | }, 29 | { 30 | "phrase": "seth green" 31 | } 32 | ], 33 | "bookmarks": [ 34 | { 35 | "uri": "https://www.apple.com/mac/", 36 | "title": "Mac - Apple", 37 | "isFavorite": true 38 | } 39 | ], 40 | "history": [ 41 | { 42 | "uri": "https://duckduckgo.com/", 43 | "title": "DuckDuckGo - Your protection, our priority.", 44 | "visitCount": 45 45 | }, 46 | { 47 | "uri": "https://www.setwiz.com/", 48 | "title": "Setup Wizard - Configuration Tool", 49 | "visitCount": 3 50 | }, 51 | { 52 | "uri": "https://app.asana.com/0/home/1206786699935999", 53 | "title": "Home - Asana", 54 | "visitCount": 6 55 | }, 56 | { 57 | "uri": "https://app.asana.com/", 58 | "title": "Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/home/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a", 75 | "uri": "duck://settings", 76 | "title": "Settings" 77 | }, 78 | { 79 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 80 | "uri": "duck://newtab", 81 | "title": "New Tab" 82 | } 83 | ] 84 | } 85 | ], 86 | "ignoredUris": [ 87 | "app.asana.com" 88 | ] 89 | }, 90 | "expectations": { 91 | "topHits": [ 92 | { 93 | "type": "historyEntry", 94 | "title": "Setup Wizard - Configuration Tool", 95 | "subtitle": "setwiz.com", 96 | "uri": "https://www.setwiz.com/", 97 | "score": 2355203 98 | } 99 | ], 100 | "searchSuggestions": [ 101 | { 102 | "type": "phrase", 103 | "title": "setlist fm", 104 | "uri": "https://duckduckgo.com/?q=setlist+fm", 105 | "score": 0 106 | }, 107 | { 108 | "type": "phrase", 109 | "title": "settings", 110 | "uri": "https://duckduckgo.com/?q=settings", 111 | "score": 0 112 | }, 113 | { 114 | "type": "phrase", 115 | "title": "settings app", 116 | "uri": "https://duckduckgo.com/?q=settings+app", 117 | "score": 0 118 | }, 119 | { 120 | "type": "phrase", 121 | "title": "setup", 122 | "uri": "https://duckduckgo.com/?q=setup", 123 | "score": 0 124 | }, 125 | { 126 | "type": "phrase", 127 | "title": "set theory", 128 | "uri": "https://duckduckgo.com/?q=set+theory", 129 | "score": 0 130 | }, 131 | { 132 | "type": "phrase", 133 | "title": "setup wizard", 134 | "uri": "https://duckduckgo.com/?q=setup+wizard", 135 | "score": 0 136 | }, 137 | { 138 | "type": "phrase", 139 | "title": "seton hall university", 140 | "uri": "https://duckduckgo.com/?q=seton+hall+university", 141 | "score": 0 142 | }, 143 | { 144 | "type": "phrase", 145 | "title": "seth green", 146 | "uri": "https://duckduckgo.com/?q=seth+green", 147 | "score": 0 148 | } 149 | ], 150 | "localSuggestions": [ 151 | { 152 | "type": "openTab", 153 | "title": "Settings", 154 | "subtitle": "DuckDuckGo", 155 | "uri": "duck://settings", 156 | "score": 2355200, 157 | "tabId": "7fd05bb0-10a2-4c38-bb0f-57ffe76bef3a" 158 | }, 159 | { 160 | "type": "internalPage", 161 | "title": "Settings → Default Browser", 162 | "subtitle": "DuckDuckGo", 163 | "uri": "duck://settings/defaultBrowser", 164 | "score": 307200 165 | }, 166 | { 167 | "type": "internalPage", 168 | "title": "Settings → Private Search", 169 | "subtitle": "DuckDuckGo", 170 | "uri": "duck://settings/privateSearch", 171 | "score": 307200 172 | } 173 | ] 174 | }, 175 | "platform": "desktop" 176 | } -------------------------------------------------------------------------------- /suggestions/special-pages-settings-no-tab-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test: duck://settings should be suggested as a local suggestion when entering 'set' even when no tab is open for it", 4 | "input": { 5 | "query": "set", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "setlist fm" 10 | }, 11 | { 12 | "phrase": "settings" 13 | }, 14 | { 15 | "phrase": "settings app" 16 | }, 17 | { 18 | "phrase": "setup" 19 | }, 20 | { 21 | "phrase": "set theory" 22 | }, 23 | { 24 | "phrase": "setup wizard" 25 | }, 26 | { 27 | "phrase": "seton hall university" 28 | }, 29 | { 30 | "phrase": "seth green" 31 | } 32 | ], 33 | "bookmarks": [ 34 | { 35 | "uri": "https://www.apple.com/mac/", 36 | "title": "Mac - Apple", 37 | "isFavorite": true 38 | } 39 | ], 40 | "history": [ 41 | { 42 | "uri": "https://duckduckgo.com/", 43 | "title": "DuckDuckGo - Your protection, our priority.", 44 | "visitCount": 45 45 | }, 46 | { 47 | "uri": "https://www.setwiz.com/", 48 | "title": "Setup Wizard - Configuration Tool", 49 | "visitCount": 3 50 | }, 51 | { 52 | "uri": "https://app.asana.com/0/home/1206786699935999", 53 | "title": "Home - Asana", 54 | "visitCount": 6 55 | }, 56 | { 57 | "uri": "https://app.asana.com/", 58 | "title": "Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/home/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 75 | "uri": "duck://newtab", 76 | "title": "New Tab" 77 | } 78 | ] 79 | } 80 | ], 81 | "ignoredUris": [ 82 | "app.asana.com" 83 | ] 84 | }, 85 | "expectations": { 86 | "topHits": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "Setup Wizard - Configuration Tool", 90 | "subtitle": "setwiz.com", 91 | "uri": "https://www.setwiz.com/", 92 | "score": 2355203 93 | } 94 | ], 95 | "searchSuggestions": [ 96 | { 97 | "type": "phrase", 98 | "title": "setlist fm", 99 | "uri": "https://duckduckgo.com/?q=setlist+fm", 100 | "score": 0 101 | }, 102 | { 103 | "type": "phrase", 104 | "title": "settings", 105 | "uri": "https://duckduckgo.com/?q=settings", 106 | "score": 0 107 | }, 108 | { 109 | "type": "phrase", 110 | "title": "settings app", 111 | "uri": "https://duckduckgo.com/?q=settings+app", 112 | "score": 0 113 | }, 114 | { 115 | "type": "phrase", 116 | "title": "setup", 117 | "uri": "https://duckduckgo.com/?q=setup", 118 | "score": 0 119 | }, 120 | { 121 | "type": "phrase", 122 | "title": "set theory", 123 | "uri": "https://duckduckgo.com/?q=set+theory", 124 | "score": 0 125 | }, 126 | { 127 | "type": "phrase", 128 | "title": "setup wizard", 129 | "uri": "https://duckduckgo.com/?q=setup+wizard", 130 | "score": 0 131 | }, 132 | { 133 | "type": "phrase", 134 | "title": "seton hall university", 135 | "uri": "https://duckduckgo.com/?q=seton+hall+university", 136 | "score": 0 137 | }, 138 | { 139 | "type": "phrase", 140 | "title": "seth green", 141 | "uri": "https://duckduckgo.com/?q=seth+green", 142 | "score": 0 143 | } 144 | ], 145 | "localSuggestions": [ 146 | { 147 | "type": "internalPage", 148 | "title": "Settings", 149 | "subtitle": "DuckDuckGo", 150 | "uri": "duck://settings", 151 | "score": 2355200 152 | }, 153 | { 154 | "type": "internalPage", 155 | "title": "Settings → Default Browser", 156 | "subtitle": "DuckDuckGo", 157 | "uri": "duck://settings/defaultBrowser", 158 | "score": 307200 159 | }, 160 | { 161 | "type": "internalPage", 162 | "title": "Settings → Private Search", 163 | "subtitle": "DuckDuckGo", 164 | "uri": "duck://settings/privateSearch", 165 | "score": 307200 166 | } 167 | ] 168 | }, 169 | "platform": "desktop" 170 | } -------------------------------------------------------------------------------- /suggestions/special-pages-settings-no-tab-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Special pages test (mobile): duck://settings should be suggested as a local suggestion when entering 'set' even when no tab is open for it", 4 | "input": { 5 | "query": "set", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "setlist fm" 10 | }, 11 | { 12 | "phrase": "settings" 13 | }, 14 | { 15 | "phrase": "settings app" 16 | }, 17 | { 18 | "phrase": "setup" 19 | }, 20 | { 21 | "phrase": "set theory" 22 | }, 23 | { 24 | "phrase": "setup wizard" 25 | }, 26 | { 27 | "phrase": "seton hall university" 28 | }, 29 | { 30 | "phrase": "seth green" 31 | } 32 | ], 33 | "bookmarks": [ 34 | { 35 | "uri": "https://www.apple.com/mac/", 36 | "title": "Mac - Apple", 37 | "isFavorite": true 38 | } 39 | ], 40 | "history": [ 41 | { 42 | "uri": "https://duckduckgo.com/", 43 | "title": "DuckDuckGo - Your protection, our priority.", 44 | "visitCount": 45 45 | }, 46 | { 47 | "uri": "https://www.setwiz.com/", 48 | "title": "Setup Wizard - Configuration Tool", 49 | "visitCount": 3 50 | }, 51 | { 52 | "uri": "https://app.asana.com/0/home/1206786699935999", 53 | "title": "Home - Asana", 54 | "visitCount": 6 55 | }, 56 | { 57 | "uri": "https://app.asana.com/", 58 | "title": "Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/home/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 75 | "uri": "duck://newtab", 76 | "title": "New Tab" 77 | } 78 | ] 79 | } 80 | ], 81 | "ignoredUris": [ 82 | "app.asana.com" 83 | ] 84 | }, 85 | "expectations": { 86 | "topHits": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "Setup Wizard - Configuration Tool", 90 | "subtitle": "", 91 | "uri": "https://www.setwiz.com/", 92 | "score": 2355203 93 | } 94 | ], 95 | "searchSuggestions": [ 96 | { 97 | "type": "phrase", 98 | "title": "setlist fm", 99 | "uri": "https://duckduckgo.com/?q=setlist+fm", 100 | "score": 0 101 | }, 102 | { 103 | "type": "phrase", 104 | "title": "settings", 105 | "uri": "https://duckduckgo.com/?q=settings", 106 | "score": 0 107 | }, 108 | { 109 | "type": "phrase", 110 | "title": "settings app", 111 | "uri": "https://duckduckgo.com/?q=settings+app", 112 | "score": 0 113 | }, 114 | { 115 | "type": "phrase", 116 | "title": "setup", 117 | "uri": "https://duckduckgo.com/?q=setup", 118 | "score": 0 119 | }, 120 | { 121 | "type": "phrase", 122 | "title": "set theory", 123 | "uri": "https://duckduckgo.com/?q=set+theory", 124 | "score": 0 125 | }, 126 | { 127 | "type": "phrase", 128 | "title": "setup wizard", 129 | "uri": "https://duckduckgo.com/?q=setup+wizard", 130 | "score": 0 131 | }, 132 | { 133 | "type": "phrase", 134 | "title": "seton hall university", 135 | "uri": "https://duckduckgo.com/?q=seton+hall+university", 136 | "score": 0 137 | }, 138 | { 139 | "type": "phrase", 140 | "title": "seth green", 141 | "uri": "https://duckduckgo.com/?q=seth+green", 142 | "score": 0 143 | } 144 | ], 145 | "localSuggestions": [ 146 | 147 | ] 148 | }, 149 | "platform": "mobile" 150 | } 151 | -------------------------------------------------------------------------------- /suggestions/suggestions-fire-window-tabs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Test standard burner tab provider returns current session burner tabs", 4 | "input": { 5 | "query": "exam", 6 | "tabIdInitiatingSearch": "44cb49ec-eaae-451c-a353-2009124d247b", 7 | "bookmarks": [], "//": "Assuming no bookmarks are needed for this test", 8 | "history": [], "//": "Assuming no history entries are needed for this test", 9 | "pinnedTabs": [ 10 | { "tabId": "c23dec90-2ca7-4324-8e81-e7278a09c58f", "title": "Pinned tab 1", "uri": "https://pinned-example.com" }, 11 | { "tabId": "07a81f08-c5da-4f1c-ba8c-0e50e2b05505", "title": "Pinned tab 2", "uri": "https://pinned-example-2.com" } 12 | ], 13 | "windows": [ 14 | { 15 | "type": "fullyFeatured", 16 | "tabs": [ 17 | { "tabId": "fcc23dee-c9db-4325-a92a-89611bdf73b6", "title": "Example", "uri": "https://example.com" }, 18 | { "tabId": "b2efe57d-4f4c-48a9-8204-4b101610107c", "title": "Selected Tab", "uri": "https://another-example.com" } 19 | ] 20 | }, 21 | { 22 | "type": "fullyFeatured", 23 | "tabs": [ 24 | { "tabId": "f53818a0-96bd-4a8b-8c8a-8a4fd72e005f", "title": "Yet Another Example", "uri": "https://yet-another-example.com" }, 25 | { "tabId": "12d7d048-1987-4488-823b-d61de0853fc1", "title": "Duplicate Example", "uri": "https://yet-another-example.com" } 26 | ] 27 | }, 28 | { 29 | "type": "fireWindow", 30 | "tabs": [ 31 | { "tabId": "a18d044d-1a5c-46c9-88b5-875634f58ec1", "title": "Burner example", "uri": "https://burner-example.com" }, 32 | { "tabId": "3f762d30-83ed-49fb-b588-8239d6a5a5fa", "title": "Burner example 2", "uri": "https://burner-example-2.com" } 33 | ] 34 | }, 35 | { 36 | "type": "fireWindow", 37 | "tabs": [ 38 | { "tabId": "44cb49ec-eaae-451c-a353-2009124d247b", "title": "Burner exa 3", "uri": "https://burner-exa-3.com" }, 39 | { "tabId": "186eb128-767f-4053-a6e1-8a9532ae1ad3", "title": "Burner example 4", "uri": "https://burner-example-4.com" }, 40 | { "tabId": "fbd713a1-a7a5-4843-8b50-3775f2ce1299", "title": "Burner example 5", "uri": "https://burner-example-5.com" } 41 | ] 42 | } 43 | ], 44 | "apiSuggestions": [], "//": "Assuming no API suggestions are needed for this test" 45 | }, 46 | "expectations": { 47 | "topHits": [], 48 | "searchSuggestions": [], 49 | "localSuggestions": [ 50 | { "type": "openTab", "title": "Burner example 4", "subtitle": "burner-example-4.com", "score": 153600, "uri": "https://burner-example-4.com", "tabId": "186eb128-767f-4053-a6e1-8a9532ae1ad3" }, 51 | { "type": "openTab", "title": "Burner example 5", "subtitle": "burner-example-5.com", "score": 153600, "uri": "https://burner-example-5.com", "tabId": "fbd713a1-a7a5-4843-8b50-3775f2ce1299" } 52 | ] 53 | }, 54 | "platform": "desktop" 55 | } 56 | -------------------------------------------------------------------------------- /suggestions/suggestions-regular-window-tabs-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Test standard open tab provider returns open tabs without current and burner tabs", 4 | "input": { 5 | "query": "tab", 6 | "tabIdInitiatingSearch": "fbd713a1-a7a5-4843-8b50-3775f2ce1299", 7 | "bookmarks": [], "//": "Assuming no bookmarks are needed for this test", 8 | "history": [], "//": "Assuming no history entries are needed for this test", 9 | "pinnedTabs": [], 10 | "windows": [ 11 | { 12 | "type": "fullyFeatured", 13 | "tabs": [ 14 | { "tabId": "438165a0-b444-4165-8b28-5b01ada66b05", "title": "Pinned tab 1", "uri": "https://pinned-example.com" }, 15 | { "tabId": "c6ef14c6-3e34-40e9-b230-1d07ea05dd7a", "title": "Pinned tab 2", "uri": "https://pinned-example-2.com" }, 16 | { "tabId": "ebbd25e2-7b32-4cc4-8e7e-ea16e829bbc8", "title": "Example", "uri": "https://example.com" }, 17 | { "tabId": "fbd713a1-a7a5-4843-8b50-3775f2ce1299", "title": "Selected Tab", "uri": "https://another-example.com" }, 18 | { "tabId": "f4386ef1-675d-43f0-b0d3-868488ab9c56", "title": "New", "uri": "duck://newtab" }, 19 | { "tabId": "8f64b4b9-2aac-4d34-bbf6-4641de875d14", "title": "Bookmarks", "uri": "duck://bookmarks" }, 20 | { "tabId": "6c4c6dfd-f8a7-421d-9b8c-a5d3ec94bea4", "title": "Settings", "uri": "duck://settings" }, 21 | { "tabId": "9e5218ca-a187-4481-a193-4284c5657ea1", "title": "Yet Another Example", "uri": "https://yet-another-example.com" }, 22 | { "tabId": "ccd16104-1655-41da-a250-04d41daa0d4f", "title": "Yet Another Example", "uri": "https://yet-another-example.com" }, 23 | { "tabId": "1daf539e-7b40-4757-a8f3-06d48016058c", "title": "Duplicate to Selected Tab", "uri": "https://another-example.com" }, 24 | { "tabId": "a0b0354b-223f-4a0b-81a7-3d7d4e5b72ff", "title": "Last Tab", "uri": "https://last.com" } 25 | ] 26 | } 27 | ], 28 | "apiSuggestions": [], "//": "Assuming no API suggestions are needed for this test" 29 | }, 30 | "expectations": { 31 | "topHits": [], 32 | "searchSuggestions": [], 33 | "localSuggestions": [ 34 | { "type": "openTab", "title": "New", "subtitle": "", "score": 153600, "uri": "duck://newtab", "tabId": "f4386ef1-675d-43f0-b0d3-868488ab9c56" }, 35 | { "type": "openTab", "title": "Pinned tab 1", "subtitle": "", "score": 102400, "uri": "https://pinned-example.com", "tabId": "438165a0-b444-4165-8b28-5b01ada66b05" }, 36 | { "type": "openTab", "title": "Pinned tab 2", "subtitle": "", "score": 102400, "uri": "https://pinned-example-2.com", "tabId": "c6ef14c6-3e34-40e9-b230-1d07ea05dd7a" }, 37 | { "type": "openTab", "title": "Selected Tab", "subtitle": "", "score": 102400, "uri": "https://another-example.com", "tabId": "fbd713a1-a7a5-4843-8b50-3775f2ce1299" } 38 | ] 39 | }, 40 | "platform": "mobile" 41 | } 42 | -------------------------------------------------------------------------------- /suggestions/suggestions-regular-window-tabs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Test standard open tab provider returns open tabs without current and burner tabs", 4 | "input": { 5 | "query": "tab", 6 | "tabIdInitiatingSearch": "fbd713a1-a7a5-4843-8b50-3775f2ce1299", 7 | "bookmarks": [], "//": "Assuming no bookmarks are needed for this test", 8 | "history": [], "//": "Assuming no history entries are needed for this test", 9 | "pinnedTabs": [ 10 | { "tabId": "438165a0-b444-4165-8b28-5b01ada66b05", "title": "Pinned tab 1", "uri": "https://pinned-example.com" }, 11 | { "tabId": "c6ef14c6-3e34-40e9-b230-1d07ea05dd7a", "title": "Pinned tab 2", "uri": "https://pinned-example-2.com" } 12 | ], 13 | "windows": [ 14 | { 15 | "type": "fullyFeatured", 16 | "tabs": [ 17 | { "tabId": "ebbd25e2-7b32-4cc4-8e7e-ea16e829bbc8", "title": "Example", "uri": "https://example.com" }, 18 | { "tabId": "fbd713a1-a7a5-4843-8b50-3775f2ce1299", "title": "Selected Tab", "uri": "https://another-example.com" }, 19 | { "tabId": "f4386ef1-675d-43f0-b0d3-868488ab9c56", "title": "New Tab", "uri": "duck://newtab" }, 20 | { "tabId": "8f64b4b9-2aac-4d34-bbf6-4641de875d14", "title": "Bookmarks", "uri": "duck://bookmarks" }, 21 | { "tabId": "6c4c6dfd-f8a7-421d-9b8c-a5d3ec94bea4", "title": "Settings", "uri": "duck://settings" } 22 | ] 23 | }, 24 | { 25 | "type": "fullyFeatured", 26 | "tabs": [ 27 | { "tabId": "9e5218ca-a187-4481-a193-4284c5657ea1", "title": "Yet Another Example", "uri": "https://yet-another-example.com" }, 28 | { "tabId": "ccd16104-1655-41da-a250-04d41daa0d4f", "title": "Yet Another Example", "uri": "https://yet-another-example.com" }, 29 | { "tabId": "1daf539e-7b40-4757-a8f3-06d48016058c", "title": "Duplicate to Selected Tab", "uri": "https://another-example.com" }, 30 | { "tabId": "a0b0354b-223f-4a0b-81a7-3d7d4e5b72ff", "title": "Last Tab", "uri": "https://last.com" } 31 | ] 32 | }, 33 | { 34 | "type": "fireWindow", 35 | "tabs": [ 36 | { "tabId": "0726775f-0d82-4cd3-8541-1578b35f0235", "title": "Burner example", "uri": "https://burner-example.com" }, 37 | { "tabId": "03257991-9054-40c7-a795-175a5bfd9848", "title": "Burner example 2", "uri": "https://burner-example-1.com" } 38 | ] 39 | }, 40 | { 41 | "type": "fireWindow", 42 | "tabs": [ 43 | { "tabId": "92513a80-b3a9-4910-8601-3108190e27ae", "title": "Burner example 3", "uri": "https://burner-example-2.com" } 44 | ] 45 | } 46 | ], 47 | "apiSuggestions": [], "//": "Assuming no API suggestions are needed for this test" 48 | }, 49 | "expectations": { 50 | "topHits": [], 51 | "searchSuggestions": [], 52 | "localSuggestions": [ 53 | { "type": "openTab", "title": "Pinned tab 1", "subtitle": "pinned-example.com", "score": 102400, "uri": "https://pinned-example.com", "tabId": "438165a0-b444-4165-8b28-5b01ada66b05" }, 54 | { "type": "openTab", "title": "Pinned tab 2", "subtitle": "pinned-example-2.com", "score": 102400, "uri": "https://pinned-example-2.com", "tabId": "c6ef14c6-3e34-40e9-b230-1d07ea05dd7a" }, 55 | { "type": "openTab", "title": "Last Tab", "subtitle": "last.com", "score": 102400, "uri": "https://last.com", "tabId": "a0b0354b-223f-4a0b-81a7-3d7d4e5b72ff" } 56 | ] 57 | }, 58 | "platform": "desktop" 59 | } 60 | -------------------------------------------------------------------------------- /suggestions/tab-deduplication-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Tab deduplication test (desktop): when 2 tabs have app.asana.com URLs and the selected tab is one of them, entering 'app.asa' doesn't suggest switching to another app.asana.com tab as it's deduplicated, but a history record for it should be shown", 4 | "input": { 5 | "query": "app.asa", 6 | "tabIdInitiatingSearch": "2d1fffb0-6bec-4263-adad-22016d6f2275", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "app.asana.com", 10 | "isNav": true 11 | }, 12 | { 13 | "phrase": "app.asana login" 14 | }, 15 | { 16 | "phrase": "app.asana.com login" 17 | }, 18 | { 19 | "phrase": "app.asada" 20 | }, 21 | { 22 | "phrase": "app.asam" 23 | }, 24 | { 25 | "phrase": "app.asanastatus.com", 26 | "isNav": true 27 | } 28 | ], 29 | "bookmarks": [], 30 | "history": [ 31 | { 32 | "uri": "https://duckduckgo.com/", 33 | "title": "DuckDuckGo - Your protection, our priority.", 34 | "visitCount": 45 35 | }, 36 | { 37 | "uri": "https://app.asana.com/-/login?u=%2Fhome%2F%40takfung", 38 | "title": "Log in - Asana", 39 | "visitCount": 3 40 | }, 41 | { 42 | "uri": "https://app.asana.com/home/@takfung?rr=977137", 43 | "title": "Asana", 44 | "visitCount": 2 45 | }, 46 | { 47 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 48 | "title": "Home - Asana", 49 | "visitCount": 8 50 | }, 51 | { 52 | "uri": "https://app.asana.com/", 53 | "title": "Asana", 54 | "visitCount": 10 55 | }, 56 | { 57 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 58 | "title": "DuckDuckGo inbox - Asana", 59 | "visitCount": 5 60 | } 61 | ], 62 | "pinnedTabs": [], 63 | "windows": [ 64 | { 65 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 66 | "type": "fullyFeatured", 67 | "tabs": [ 68 | { 69 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 70 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 71 | "title": "Home - Asana" 72 | }, 73 | { 74 | "tabId": "9dda832f-d6d4-490a-9837-c6a9d026d74b", 75 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 76 | "title": "Inbox - Asana" 77 | }, 78 | { 79 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 80 | "uri": "duck://newtab", 81 | "title": "New Tab" 82 | }, 83 | { 84 | "tabId": "9fda832f-d6d4-490a-9837-c6a9d026d74a", 85 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 86 | "title": "DuckDuckGo inbox - Asana" 87 | }, 88 | { 89 | "tabId": "9ada832f-d6d4-490a-9837-c6a9d026d74c", 90 | "uri": "https://app.asana.com/0/inbox/1206786699935900", 91 | "title": "00 – Asana" 92 | } 93 | ] 94 | } 95 | ], 96 | "ignoredUris": [] 97 | }, 98 | "expectations": { 99 | "topHits": [ 100 | { 101 | "type": "historyEntry", 102 | "title": "Asana", 103 | "subtitle": "app.asana.com", 104 | "uri": "https://app.asana.com/", 105 | "score": 2355210 106 | }, 107 | { 108 | "type": "historyEntry", 109 | "title": "Home - Asana", 110 | "subtitle": "app.asana.com/0/inbox/1206786699935999", 111 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 112 | "score": 307208 113 | } 114 | ], 115 | "searchSuggestions": [ 116 | { 117 | "type": "website", 118 | "title": "app.asana.com", 119 | "subtitle": "", 120 | "uri": "http://app.asana.com", 121 | "score": 0 122 | }, 123 | { 124 | "type": "phrase", 125 | "title": "app.asana login", 126 | "uri": "https://duckduckgo.com/?q=app.asana+login", 127 | "score": 0 128 | }, 129 | { 130 | "type": "phrase", 131 | "title": "app.asana.com login", 132 | "uri": "https://duckduckgo.com/?q=app.asana.com+login", 133 | "score": 0 134 | }, 135 | { 136 | "type": "phrase", 137 | "title": "app.asada", 138 | "uri": "https://duckduckgo.com/?q=app.asada", 139 | "score": 0 140 | }, 141 | { 142 | "type": "phrase", 143 | "title": "app.asam", 144 | "uri": "https://duckduckgo.com/?q=app.asam", 145 | "score": 0 146 | }, 147 | { 148 | "type": "website", 149 | "title": "app.asanastatus.com", 150 | "subtitle": "", 151 | "uri": "http://app.asanastatus.com", 152 | "score": 0 153 | } 154 | ], 155 | "localSuggestions": [ 156 | { 157 | "type": "historyEntry", 158 | "title": "Log in - Asana", 159 | "subtitle": "app.asana.com/-/login?u=%2Fhome%2F%40takfung", 160 | "uri": "https://app.asana.com/-/login?u=%2Fhome%2F%40takfung", 161 | "score": 307203 162 | }, 163 | { 164 | "type": "historyEntry", 165 | "title": "Asana", 166 | "subtitle": "app.asana.com/home/@takfung?rr=977137", 167 | "uri": "https://app.asana.com/home/@takfung?rr=977137", 168 | "score": 307202 169 | }, 170 | { 171 | "score" : 307200, 172 | "subtitle" : "app.asana.com\/0\/inbox\/1206786699935900", 173 | "tabId" : "9ADA832F-D6D4-490A-9837-C6A9D026D74C", 174 | "title" : "00 – Asana", 175 | "type" : "openTab", 176 | "uri" : "https:\/\/app.asana.com\/0\/inbox\/1206786699935900" 177 | } 178 | ] 179 | }, 180 | "platform": "desktop" 181 | } 182 | -------------------------------------------------------------------------------- /suggestions/tab-deduplication-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Tab deduplication test (mobile): when 2 tabs have app.asana.com URLs and the selected tab is one of them, entering 'app.asa' doesn't suggest switching to another app.asana.com tab as it's deduplicated, but a history record for it should be shown", 4 | "input": { 5 | "query": "app.asa", 6 | "tabIdInitiatingSearch": "2d1fffb0-6bec-4263-adad-22016d6f2275", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "app.asana.com" 10 | }, 11 | { 12 | "phrase": "app.asana login" 13 | }, 14 | { 15 | "phrase": "app.asana.com login" 16 | }, 17 | { 18 | "phrase": "app.asada" 19 | }, 20 | { 21 | "phrase": "app.asam" 22 | }, 23 | { 24 | "phrase": "app.asanastatus.com" 25 | } 26 | ], 27 | "bookmarks": [], 28 | "history": [ 29 | { 30 | "uri": "https://duckduckgo.com/", 31 | "title": "DuckDuckGo - Your protection, our priority.", 32 | "visitCount": 45 33 | }, 34 | { 35 | "uri": "https://app.asana.com/-/login?u=%2Fhome%2F%40takfung", 36 | "title": "Log in - Asana", 37 | "visitCount": 3 38 | }, 39 | { 40 | "uri": "https://app.asana.com/home/@takfung?rr=977137", 41 | "title": "Asana", 42 | "visitCount": 2 43 | }, 44 | { 45 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 46 | "title": "Home - Asana", 47 | "visitCount": 13 48 | }, 49 | { 50 | "uri": "https://app.asana.com/", 51 | "title": "Asana", 52 | "visitCount": 10 53 | } 54 | ], 55 | "pinnedTabs": [], 56 | "windows": [ 57 | { 58 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 59 | "type": "fullyFeatured", 60 | "tabs": [ 61 | { 62 | "tabId": "2d1fffb0-6bec-4263-adad-22016d6f2275", 63 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 64 | "title": "Home - Asana" 65 | }, 66 | { 67 | "tabId": "9dda832f-d6d4-490a-9837-c6a9d026d74b", 68 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 69 | "title": "Inbox - Asana" 70 | }, 71 | { 72 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 73 | "uri": "duck://newtab", 74 | "title": "New Tab" 75 | }, 76 | { 77 | "tabId": "9fda832f-d6d4-490a-9837-c6a9d026d74a", 78 | "uri": "https://app.asana.com/0/inbox/1206786699935999", 79 | "title": "DuckDuckGo inbox - Asana" 80 | }, 81 | { 82 | "tabId": "9ada832f-d6d4-490a-9837-c6a9d026d74c", 83 | "uri": "https://app.asana.com/0/inbox/1206786699935900", 84 | "title": "00 – Asana" 85 | } 86 | ] 87 | } 88 | ], 89 | "ignoredUris": [] 90 | }, 91 | "expectations": { 92 | "topHits": [ 93 | { 94 | "type": "historyEntry", 95 | "title": "Asana", 96 | "subtitle": "", 97 | "uri": "https://app.asana.com/", 98 | "score": 2355210 99 | }, 100 | { 101 | "subtitle" : "", 102 | "tabId" : "2D1FFFB0-6BEC-4263-ADAD-22016D6F2275", 103 | "title" : "Home - Asana", 104 | "type" : "openTab", 105 | "uri" : "https:\/\/app.asana.com\/0\/inbox\/1206786699935999", 106 | "score": 307213 107 | } 108 | ], 109 | "searchSuggestions": [ 110 | { 111 | "type": "phrase", 112 | "title": "app.asana.com", 113 | "uri": "https://duckduckgo.com/?q=app.asana.com", 114 | "score": 0 115 | }, 116 | { 117 | "type": "phrase", 118 | "title": "app.asana login", 119 | "uri": "https://duckduckgo.com/?q=app.asana+login", 120 | "score": 0 121 | }, 122 | { 123 | "type": "phrase", 124 | "title": "app.asana.com login", 125 | "uri": "https://duckduckgo.com/?q=app.asana.com+login", 126 | "score": 0 127 | }, 128 | { 129 | "type": "phrase", 130 | "title": "app.asada", 131 | "uri": "https://duckduckgo.com/?q=app.asada", 132 | "score": 0 133 | }, 134 | { 135 | "type": "phrase", 136 | "title": "app.asam", 137 | "uri": "https://duckduckgo.com/?q=app.asam", 138 | "score": 0 139 | }, 140 | { 141 | "type": "phrase", 142 | "title": "app.asanastatus.com", 143 | "uri": "https://duckduckgo.com/?q=app.asanastatus.com", 144 | "score": 0 145 | } 146 | ], 147 | "localSuggestions": [ 148 | { 149 | "type": "historyEntry", 150 | "title": "Log in - Asana", 151 | "subtitle": "", 152 | "uri": "https://app.asana.com/-/login?u=%2Fhome%2F%40takfung", 153 | "score": 307203 154 | }, 155 | { 156 | "type": "historyEntry", 157 | "title": "Asana", 158 | "subtitle": "", 159 | "uri": "https://app.asana.com/home/@takfung?rr=977137", 160 | "score": 307202 161 | } 162 | ] 163 | }, 164 | "platform": "mobile" 165 | } 166 | -------------------------------------------------------------------------------- /suggestions/top-hits-history-rules-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Top Hits History Rules test: history entries are only included in top hits if they have > 3 visits or are root domains", 4 | "input": { 5 | "query": "his", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "history channel" 10 | }, 11 | { 12 | "phrase": "history com" 13 | } 14 | ], 15 | "bookmarks": [], 16 | "history": [ 17 | { 18 | "uri": "https://www.history.com/", 19 | "title": "HISTORY | Watch Full Episodes of Your Favorite Shows", 20 | "visitCount": 2 21 | }, 22 | { 23 | "uri": "https://www.historictours.com/subpage.html", 24 | "title": "Historic Tours Subpage", 25 | "visitCount": 6 26 | }, 27 | { 28 | "uri": "https://historian.org/", 29 | "title": "Historian.org - Root domain with low visits", 30 | "visitCount": 1 31 | } 32 | ], 33 | "pinnedTabs": [], 34 | "windows": [ 35 | { 36 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 37 | "type": "fullyFeatured", 38 | "tabs": [ 39 | { 40 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 41 | "uri": "duck://newtab", 42 | "title": "New Tab" 43 | } 44 | ] 45 | } 46 | ], 47 | "ignoredUris": [] 48 | }, 49 | "expectations": { 50 | "topHits": [ 51 | { 52 | "type": "historyEntry", 53 | "title": "HISTORY | Watch Full Episodes of Your Favorite Shows", 54 | "subtitle": "history.com", 55 | "uri": "https://www.history.com/", 56 | "score": 2355202 57 | }, 58 | { 59 | "type": "historyEntry", 60 | "title": "Historian.org - Root domain with low visits", 61 | "subtitle": "historian.org", 62 | "uri": "https://historian.org/", 63 | "score": 2355201 64 | } 65 | ], 66 | "searchSuggestions": [ 67 | { 68 | "type": "phrase", 69 | "title": "history channel", 70 | "uri": "https://duckduckgo.com/?q=history+channel", 71 | "score": 0 72 | }, 73 | { 74 | "type": "phrase", 75 | "title": "history com", 76 | "uri": "https://duckduckgo.com/?q=history+com", 77 | "score": 0 78 | } 79 | ], 80 | "localSuggestions": [ 81 | { 82 | "type" : "internalPage", 83 | "title" : "History", 84 | "subtitle" : "DuckDuckGo", 85 | "uri" : "duck:\/\/history", 86 | "score" : 2355200 87 | }, 88 | { 89 | "type": "historyEntry", 90 | "title": "Historic Tours Subpage", 91 | "subtitle": "historictours.com/subpage.html", 92 | "uri": "https://www.historictours.com/subpage.html", 93 | "score": 307206 94 | } 95 | ] 96 | }, 97 | "platform": "desktop" 98 | } 99 | -------------------------------------------------------------------------------- /suggestions/top-hits-history-rules-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Top Hits History Rules test for mobile: history entries are only included in top hits if they have > 3 visits or are root domains", 4 | "input": { 5 | "query": "his", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "history channel" 10 | }, 11 | { 12 | "phrase": "history com" 13 | } 14 | ], 15 | "bookmarks": [], 16 | "history": [ 17 | { 18 | "uri": "https://www.history.com/", 19 | "title": "HISTORY | Watch Full Episodes of Your Favorite Shows", 20 | "visitCount": 2 21 | }, 22 | { 23 | "uri": "https://www.historictours.com/subpage.html", 24 | "title": "Historic Tours Subpage", 25 | "visitCount": 6 26 | }, 27 | { 28 | "uri": "https://historian.org/", 29 | "title": "Historian.org - Root domain with low visits", 30 | "visitCount": 1 31 | } 32 | ], 33 | "pinnedTabs": [], 34 | "windows": [ 35 | { 36 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 37 | "type": "fullyFeatured", 38 | "tabs": [ 39 | { 40 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 41 | "uri": "duck://newtab", 42 | "title": "New Tab" 43 | } 44 | ] 45 | } 46 | ], 47 | "ignoredUris": [] 48 | }, 49 | "expectations": { 50 | "topHits": [ 51 | { 52 | "type": "historyEntry", 53 | "title": "HISTORY | Watch Full Episodes of Your Favorite Shows", 54 | "subtitle": "", 55 | "uri": "https://www.history.com/", 56 | "score": 2355202 57 | }, 58 | { 59 | "type": "historyEntry", 60 | "title": "Historian.org - Root domain with low visits", 61 | "subtitle": "", 62 | "uri": "https://historian.org/", 63 | "score": 2355201 64 | } 65 | ], 66 | "searchSuggestions": [ 67 | { 68 | "type": "phrase", 69 | "title": "history channel", 70 | "uri": "https://duckduckgo.com/?q=history+channel", 71 | "score": 0 72 | }, 73 | { 74 | "type": "phrase", 75 | "title": "history com", 76 | "uri": "https://duckduckgo.com/?q=history+com", 77 | "score": 0 78 | } 79 | ], 80 | "localSuggestions": [ 81 | { 82 | "type": "historyEntry", 83 | "title": "Historic Tours Subpage", 84 | "subtitle": "", 85 | "uri": "https://www.historictours.com/subpage.html", 86 | "score": 307206 87 | } 88 | ] 89 | }, 90 | "platform": "mobile" 91 | } 92 | -------------------------------------------------------------------------------- /suggestions/top-hits-open-tab-handling-desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Top Hits Open Tab Handling test: When a URL exists in both history and as an open tab, both versions should be shown in top hits with non-tab version first", 4 | "input": { 5 | "query": "git", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "github" 10 | }, 11 | { 12 | "phrase": "gitlab" 13 | } 14 | ], 15 | "bookmarks": [], 16 | "history": [ 17 | { 18 | "uri": "https://github.com/", 19 | "title": "GitHub: Let's build from here", 20 | "visitCount": 28 21 | }, 22 | { 23 | "uri": "https://gitlab.com/", 24 | "title": "GitLab: DevSecOps Platform", 25 | "visitCount": 14 26 | }, 27 | { 28 | "uri": "https://git-scm.com/", 29 | "title": "Git", 30 | "visitCount": 5 31 | } 32 | ], 33 | "pinnedTabs": [], 34 | "windows": [ 35 | { 36 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 37 | "type": "fullyFeatured", 38 | "tabs": [ 39 | { 40 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 41 | "uri": "https://github.com/", 42 | "title": "GitHub: Let's build from here" 43 | }, 44 | { 45 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 46 | "uri": "duck://newtab", 47 | "title": "New Tab" 48 | } 49 | ] 50 | } 51 | ], 52 | "ignoredUris": [] 53 | }, 54 | "expectations": { 55 | "topHits": [ 56 | { 57 | "type": "historyEntry", 58 | "title": "GitHub: Let's build from here", 59 | "subtitle": "github.com", 60 | "uri": "https://github.com/", 61 | "score": 2355228 62 | }, 63 | { 64 | "type": "openTab", 65 | "title": "GitHub: Let's build from here", 66 | "subtitle": "github.com", 67 | "uri": "https://github.com/", 68 | "score": 2355228, 69 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275" 70 | } 71 | ], 72 | "searchSuggestions": [ 73 | { 74 | "type": "phrase", 75 | "title": "github", 76 | "uri": "https://duckduckgo.com/?q=github", 77 | "score": 0 78 | }, 79 | { 80 | "type": "phrase", 81 | "title": "gitlab", 82 | "uri": "https://duckduckgo.com/?q=gitlab", 83 | "score": 0 84 | } 85 | ], 86 | "localSuggestions": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "GitLab: DevSecOps Platform", 90 | "subtitle": "gitlab.com", 91 | "uri": "https://gitlab.com/", 92 | "score": 2355214 93 | }, 94 | { 95 | "type": "historyEntry", 96 | "title": "Git", 97 | "subtitle": "git-scm.com", 98 | "uri": "https://git-scm.com/", 99 | "score": 2355205 100 | } 101 | ] 102 | }, 103 | "platform": "desktop" 104 | } -------------------------------------------------------------------------------- /suggestions/top-hits-open-tab-handling-mobile.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./search-suggestion-test-scenario-schema.json", 3 | "description": "Top Hits Open Tab Handling test for mobile: When a URL exists in both history and as an open tab, both versions should be shown in top hits with non-tab version first", 4 | "input": { 5 | "query": "git", 6 | "tabIdInitiatingSearch": "f63c4963-de07-411b-897c-a40bb05fa7e2", 7 | "apiSuggestions": [ 8 | { 9 | "phrase": "github" 10 | }, 11 | { 12 | "phrase": "gitlab" 13 | } 14 | ], 15 | "bookmarks": [], 16 | "history": [ 17 | { 18 | "uri": "https://github.com/", 19 | "title": "GitHub: Let's build from here", 20 | "visitCount": 28 21 | }, 22 | { 23 | "uri": "https://gitlab.com/", 24 | "title": "GitLab: DevSecOps Platform", 25 | "visitCount": 14 26 | }, 27 | { 28 | "uri": "https://git-scm.com/", 29 | "title": "Git", 30 | "visitCount": 5 31 | } 32 | ], 33 | "pinnedTabs": [], 34 | "windows": [ 35 | { 36 | "windowId": "c6d47aa2-8c1a-4c50-8877-3745bd1865fb", 37 | "type": "fullyFeatured", 38 | "tabs": [ 39 | { 40 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275", 41 | "uri": "https://github.com/", 42 | "title": "GitHub: Let's build from here" 43 | }, 44 | { 45 | "tabId": "f63c4963-de07-411b-897c-a40bb05fa7e2", 46 | "uri": "duck://newtab", 47 | "title": "New Tab" 48 | } 49 | ] 50 | } 51 | ], 52 | "ignoredUris": [] 53 | }, 54 | "expectations": { 55 | "topHits": [ 56 | { 57 | "type": "historyEntry", 58 | "title": "GitHub: Let's build from here", 59 | "subtitle": "", 60 | "uri": "https://github.com/", 61 | "score": 2355228 62 | }, 63 | { 64 | "type": "openTab", 65 | "title": "GitHub: Let's build from here", 66 | "subtitle": "", 67 | "uri": "https://github.com/", 68 | "score": 2355228, 69 | "tabId": "1d1fffb0-6bec-4263-adad-22016d6f2275" 70 | } 71 | ], 72 | "searchSuggestions": [ 73 | { 74 | "type": "phrase", 75 | "title": "github", 76 | "uri": "https://duckduckgo.com/?q=github", 77 | "score": 0 78 | }, 79 | { 80 | "type": "phrase", 81 | "title": "gitlab", 82 | "uri": "https://duckduckgo.com/?q=gitlab", 83 | "score": 0 84 | } 85 | ], 86 | "localSuggestions": [ 87 | { 88 | "type": "historyEntry", 89 | "title": "GitLab: DevSecOps Platform", 90 | "subtitle": "", 91 | "uri": "https://gitlab.com/", 92 | "score": 2355214 93 | }, 94 | { 95 | "type": "historyEntry", 96 | "title": "Git", 97 | "subtitle": "", 98 | "uri": "https://git-scm.com/", 99 | "score": 2355205 100 | } 101 | ] 102 | }, 103 | "platform": "mobile" 104 | } 105 | -------------------------------------------------------------------------------- /tracker-radar-tests/TR-domain-matching/README.md: -------------------------------------------------------------------------------- 1 | # Tracker Blocking Related Tests 2 | 3 | This folder contains tests for the following features: 4 | 5 | - Tracker blocking - https://app.asana.com/0/1198207348643509/1199103718890844 6 | - CNAME cloaking - https://app.asana.com/0/1198207348643509/1199093921854069 7 | - Tracker allowlist feature in the Privacy Configuration - https://app.asana.com/0/1198207348643509/1199981227466169 8 | - Surrogates - https://app.asana.com/0/1198207348643509/1199093921854088/f 9 | 10 | ## Structure 11 | 12 | ### Tracker blocking, CNAME cloaking and surrogates 13 | 14 | Files in the folder: 15 | - `domain_matching_tests.json` - tests for tracker blocking, CNAME cloaking and surrogates 16 | - `tracker_radar_reference.json` - reference blocklist file that should be used with `domain_matching_tests.json` tests 17 | - `surrogates.txt` - reference surrogates file that should be used with `domain_matching_tests.json` tests 18 | 19 | Test suite specific fields: 20 | - `siteURL` - URL - page where request in question is made 21 | - `requestURL` - URL - request in question 22 | - `requestType` - mostly "image" or "script", but can be any of https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest/ResourceType - type of the resource being fetched 23 | - `expectAction` - one of: null (don't block, not on a blocklist), ignore (don't block, exception), block, redirect (when should be replaced with a surrogate) - expected action that client should take 24 | - `expectRedirect` - string - only if action is "redirect". This should contain url of the surrogate (base64'ed version of the correct surrogate file) 25 | - `expectExpression` - string - only if action is "redirect". This should contain the expression to validate if the surrogate was correctly injected. 26 | 27 | #### ⚠️ Note ⚠️ 28 | 29 | For tests with `expectAction == redirect` to be valid you need to assert either `expectRedirect` and/or `expectExpression` (at least one). The reason we have two different fields is because some platforms (like macOS/iOS) cannot assert against `expectRedirect`. 30 | 31 | #### Platform exceptions 32 | 33 | - "ports are ignored when matching rules" disabled for Apple platform ([bug report](https://app.asana.com/0/1163321984198618/1201849181617632/f)) 34 | 35 | ### Privacy config allowlist 36 | 37 | Files in the folder: 38 | - `tracker_allowlist_matching_tests.json` - test for tracker allowlist feature - ⚠️ those tests don't follow the format used by other tests ⚠️ 39 | - `tracker_allowlist_tds_references.json` - reference blocklist file that should be used with `tracker_allowlist_matching_tests.json` tests 40 | - `tracker_allowlist_references.json` - reference privacy config file that should be used with `tracker_allowlist_matching_tests.json` tests 41 | 42 | Test suite specific fields: 43 | - `site` - URL - page where request in question is made 44 | - `request` - URL - request in question 45 | - `isAllowlisted` - bool - if request is expected to be allowlisted or not 46 | 47 | ## Platform exceptions 48 | 49 | - android-browser: Blocks when CNAME is same-entity. https://app.asana.com/0/414730916066338/1203532296782616/f 50 | - ios-browser: Exceptions for options2 trackers caused by inability to construct rules from current tds as the last ignore rule invalidates all previous rules. https://app.asana.com/0/1203790657351911/1204149290597759/f; One exception for tracker.test is because Apple platforms are not going to receive any trackers with default action block and options - it is again due to inability to construct rules with the current algorithm. We will follow up in https://app.asana.com/0/1200443608678338/1204431436175798/f; Some exceptions for tests that use request types other than image or stylesheet. Scoped to be fixed: https://app.asana.com/0/0/1204643428963159/f 51 | -------------------------------------------------------------------------------- /tracker-radar-tests/TR-domain-matching/surrogates.txt: -------------------------------------------------------------------------------- 1 | # This file contains "surrogates". Surrogates are small scripts that our apps and extensions serve in place of trackers that cause site breakage when blocked. 2 | # Learn more: https://github.com/duckduckgo/tracker-surrogates 3 | surrogates.test/tracker application/javascript 4 | (function() {window.surrogate1=true})(); 5 | 6 | something.else.com/script.js application/javascript 7 | (function() {window.surrogate2=true})(); -------------------------------------------------------------------------------- /tracker-radar-tests/TR-domain-matching/tracker_allowlist_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowlist-tracker-1.com": { 3 | "rules" : [ 4 | { 5 | "rule": "allowlist-tracker-1.com/videos.js", 6 | "domains": ["testsite.com"], 7 | "reason": "match single resource on single site" 8 | }, 9 | { 10 | "rule": "allowlist-tracker-1.com/hosttest.js", 11 | "domains": ["host1.testsite.com", "a.c.testsite.com"], 12 | "reason": "match on subdomain" 13 | } 14 | ] 15 | }, 16 | "allowlist-tracker-2.com": { 17 | "rules" : [ 18 | { 19 | "rule": "videos.allowlist-tracker-2.com/a.js", 20 | "domains": ["testsite.com"], 21 | "reason": "specific subdomain rule" 22 | }, 23 | { 24 | "rule": "allowlist-tracker-2.com/comments/", 25 | "domains": [""], 26 | "reason": "match all sites and all paths" 27 | }, 28 | { 29 | "rule": "allowlist-tracker-2.com/login.js", 30 | "domains": [""], 31 | "reason": "match single resource on all sites" 32 | } 33 | ] 34 | }, 35 | "allowlist-tracker-3.com": { 36 | "rules": [ 37 | { 38 | "rule": "allowlist-tracker-3.com", 39 | "domains": [""], 40 | "reason": "match all requests" 41 | } 42 | ] 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /tracker-radar-tests/TR-domain-matching/tracker_allowlist_tds_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "trackers": { 3 | "allowlist-tracker-1.com": { 4 | "domain": "allowlist-tracker-1.com", 5 | "owner": { 6 | "name": "Test Site for Tracker Blocking", 7 | "displayName": "allowlist-tracker-1.com", 8 | "privacyPolicy": "", 9 | "url": "http://allowlist-tracker-1.com" 10 | }, 11 | "prevalence": 0.1, 12 | "fingerprinting": 3, 13 | "cookies": 0.1, 14 | "categories": [], 15 | "default": "block" 16 | }, 17 | "allowlist-tracker-2.com": { 18 | "domain": "allowlist-tracker-2.com", 19 | "owner": { 20 | "name": "Test Site for Tracker Blocking", 21 | "displayName": "allowlist-tracker-2.com", 22 | "privacyPolicy": "", 23 | "url": "http://allowlist-tracker-2.com" 24 | }, 25 | "prevalence": 0.1, 26 | "fingerprinting": 3, 27 | "cookies": 0.1, 28 | "categories": [], 29 | "default": "block" 30 | }, 31 | "allowlist-tracker-3.com": { 32 | "domain": "allowlist-tracker-3.com", 33 | "owner": { 34 | "name": "Test Site for Tracker Blocking", 35 | "displayName": "allowlist-tracker-3.com", 36 | "privacyPolicy": "", 37 | "url": "http://allowlist-tracker-3.com" 38 | }, 39 | "prevalence": 0.1, 40 | "fingerprinting": 3, 41 | "cookies": 0.1, 42 | "categories": [], 43 | "default": "block" 44 | } 45 | }, 46 | "entities": { 47 | "Test Site for Tracker Blocking": { 48 | "domains": [ 49 | "allowlist-tracker-1.com", 50 | "allowlist-tracker-2.com", 51 | "allowlist-tracker-3.com" 52 | ], 53 | "prevalence": 0.1, 54 | "displayName": "Test Site for Tracker Blocking" 55 | } 56 | }, 57 | "cnames": { 58 | "bad.cnames.test": "allowlist-tracker-1.com" 59 | }, 60 | "domains": { 61 | "allowlist-tracker-1.com": "Test Site for Tracker Blocking", 62 | "allowlist-tracker-2.com": "Test Site for Tracker Blocking", 63 | "allowlist-tracker-3.com": "Test Site for Tracker Blocking" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /url-parameters/README.md: -------------------------------------------------------------------------------- 1 | # URL Parameters Tests 2 | 3 | Privacy Feature: https://app.asana.com/0/1198207348643509/1201469937577208/f 4 | 5 | ## Goals 6 | 7 | This set of tests verifies the implementation of URL parameter stripping. In particular it focuses on verifying that: 8 | 9 | - Tracking parameters identified in the config are able to be removed 10 | - Non-tracking parameters are preserved in the URL 11 | 12 | ## Structure 13 | 14 | There are multiple sets of tests in `tests.json`. 15 | 16 | Test suite specific fields: 17 | 18 | - testURL - string - URL that needs to be rewritten 19 | - expectURL - string - The expected URL to be returned (if string is empty native implementation should return nil) 20 | 21 | ## Pseudo-code implementation 22 | 23 | ``` 24 | for $testSet in test.json 25 | loadRemoteConfig($testSet.referenceConfig) 26 | 27 | for $test in $testSet 28 | $cleanURL = LinkCleaner.removeTrackingParameters($testURL) 29 | 30 | expect($cleanURL === $test.expectURL) 31 | ``` 32 | -------------------------------------------------------------------------------- /url-parameters/config_reference.json: -------------------------------------------------------------------------------- 1 | { 2 | "readme": "This configuration has only the tracking parameters feature.", 3 | "features": { 4 | "trackingParameters": { 5 | "state": "enabled", 6 | "exceptions": [ 7 | { 8 | "domain": "allowlisted.com", 9 | "reason": "site breakage" 10 | } 11 | ], 12 | "settings": { 13 | "parameters": [ 14 | "utm_source", 15 | "utm_medium", 16 | "utm_campaign", 17 | "utm_term", 18 | "utm_content", 19 | "gclid", 20 | "fbclid", 21 | "fb_action_ids", 22 | "fb_action_types", 23 | "fb_source", 24 | "fb_ref", 25 | "ga_source", 26 | "ga_medium", 27 | "ga_term", 28 | "ga_content", 29 | "ga_campaign", 30 | "ga_place", 31 | "action_object_map", 32 | "action_type_map", 33 | "action_ref_map", 34 | "gs_l", 35 | "mkt_tok", 36 | "hmb_campaign", 37 | "hmb_source", 38 | "hmb_medium", 39 | "bad." 40 | ] 41 | } 42 | } 43 | }, 44 | "version": 1635943904459, 45 | "unprotectedTemporary": [] 46 | } 47 | --------------------------------------------------------------------------------