From fd55b2aac5b3a8d8d7064a757e78e7cd907e3fa1 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Tue, 30 Dec 2025 23:06:49 +0000 Subject: [PATCH 1/2] end2end tests: add logging for chrome --- tests/test_end2end.py | 2 +- tests/webdriver_utils.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_end2end.py b/tests/test_end2end.py index 802e83e..bb70de3 100644 --- a/tests/test_end2end.py +++ b/tests/test_end2end.py @@ -99,7 +99,7 @@ def test_capture_custom_endpoint(*, addon: Addon, driver: Driver, backend: Backe # due to broad permissions given by content script to detect dark mode and set icon accordingly. # See comment about detect_dark_mode.js in generate_manifest.js wait_for_permissions = browser.name != 'firefox' - if wait_for_permissions: + if wait_for_permissions and browser.headless: pytest.skip("This test requires GUI to confirm permission prompts") addon.options_page.change_endpoint( endpoint=f'http://{hostname}:{backend.port}/capture', diff --git a/tests/webdriver_utils.py b/tests/webdriver_utils.py index b1aa8e8..82212f8 100644 --- a/tests/webdriver_utils.py +++ b/tests/webdriver_utils.py @@ -220,6 +220,9 @@ def get_webdriver( # regular --headless doesn't support extensions for some reason cr_options.add_argument('--headless=new') + # not sure this does anything?? + cr_options.set_capability('goog:loggingPrefs', {'browser': 'ALL'}) + # ugh. this is necessary for chrome to consider extension pages as part of normal tabs # https://github.com/SeleniumHQ/selenium/issues/15685 # https://issues.chromium.org/issues/416666972 @@ -229,7 +232,8 @@ def get_webdriver( # generally 'selenium manager' downloads the correct driver version itself chromedriver_bin: str | None = None # default - service = webdriver.ChromeService(executable_path=chromedriver_bin) + # 2 means stderr (seems like otherwise it's not logging at all) + service = webdriver.ChromeService(executable_path=chromedriver_bin, log_output=2) driver = webdriver.Chrome(service=service, options=cr_options) version_data['chromedriverVersion'] = driver.capabilities['chrome']['chromedriverVersion'] From c3b39456e3b684d84e0ad89906eeb3579e11728f Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Sat, 3 Jan 2026 16:11:36 +0000 Subject: [PATCH 2/2] extension: some improvements for build files to prepare for publishing new version --- README.md | 1 + extension/README.md | 5 +++++ extension/build | 40 ++++++++++++++++++---------------- extension/generate_manifest.js | 11 +++++----- extension/package.json | 6 ++--- extension/rollup.config.js | 3 ++- 6 files changed, 38 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index d84c201..2aeb4c5 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ In addition: * `storage` for settings * `notifications` for showing notification * `activeTab` for requesting page info +* `scripting` for getting text selection on the page # Building & developing The most up-to-date instructions should be in [CI config](./.circleci/config.yml). diff --git a/extension/README.md b/extension/README.md index b5aaa62..0961173 100644 --- a/extension/README.md +++ b/extension/README.md @@ -1,3 +1,8 @@ +- manifest version: both chrome and firefox store versions are on manifest v3 now + +# permissions used +See main `README.md`. + # `package.json` comments ## browserslist Not used by the extension (since `rollup-plugin-typescript` is looking at `tsconfig.json`), but it's used by babel/jest? diff --git a/extension/build b/extension/build index 34b4304..745bba8 100755 --- a/extension/build +++ b/extension/build @@ -1,10 +1,11 @@ #!/usr/bin/env python3 import argparse +import json import os -from subprocess import check_call -from pathlib import Path import subprocess import sys +from pathlib import Path +from subprocess import check_call # right, specifying id in manifest doesn't seem to work # AMO responds with: Server response: Duplicate add-on ID found. (status: 400) @@ -71,9 +72,8 @@ def main() -> None: '--output=json', '--no-config-discovery', # prevent it from printing "Applying config file" to stdout silent=True, # otherwise craps in stdout - method=lambda cmd: subprocess.run(cmd, stdout=subprocess.PIPE, text=True), + method=lambda cmd: subprocess.run(cmd, check=False, stdout=subprocess.PIPE, text=True), ) - import json j = json.loads(res.stdout) errors = j['errors'] if len(errors) == 0: @@ -97,7 +97,7 @@ def main() -> None: } if args.watch: - check_call([npm, 'run', 'watch'], env=env, cwd=str(THISDIR)) # TODO exec instead? + check_call([npm, 'run', 'watch'], env=env, cwd=str(THISDIR)) # todo exec instead? return check_call([npm, 'run', 'build'], env=env, cwd=str(THISDIR)) @@ -108,32 +108,34 @@ def main() -> None: webext_lint() # TODO move somewhere more appropriate.. - webext('build', '-o') # -o overwrites existing artifact + webext('build', '-o') # -o overwrites existing artifact if args.release: - assert args.lint # TODO not sure.. - - def firefox_publish_args(): - from firefox_dev_secrets import API_KEY, API_SECRET - return [ - '--artifacts-dir', str(artifacts_dir), - '--api-key' , API_KEY, - '--api-secret' , API_SECRET, - # seems like webext sign requires addon id to be in manifest now - ] + assert args.lint # TODO not sure.. if args.publish not in {None, 'skip'}: # 'skip' mode is useful to build exactly same build as for the store, but without actually uploading assert args.lint assert args.release + + check_call(['git', 'diff-index', 'HEAD']) # ensure repo is clean + source_zip = base_ext_dir / f'extension-source-{target}.zip' + check_call(['git', 'archive', 'HEAD', '--output', source_zip], cwd=THISDIR) # cwd so we only package the extension + if 'firefox' in target: + from firefox_dev_secrets import API_KEY, API_SECRET check_call([ npm, 'run', 'web-ext', '--', - 'sign', '--use-submission-api', + 'sign', '--channel', args.publish, '--source-dir', str(ext_dir), - *firefox_publish_args(), + '--artifacts-dir', str(artifacts_dir), + '--api-key' , API_KEY, + '--api-secret' , API_SECRET, + # seems like webext sign requires addon id to be in manifest now + '--amo-metadata' , THISDIR / 'amo-metadata.json', + '--upload-source-code', source_zip, ]) elif target == 'chrome': assert args.publish == 'listed' # no notion of unlisted on chrome store? @@ -151,7 +153,7 @@ def main() -> None: # TODO trusted testers? ]) else: - raise RuntimeError("{target} is not supported for publishing yet".format(target=target)) + raise RuntimeError(f"{target} is not supported for publishing yet") if __name__ == '__main__': main() diff --git a/extension/generate_manifest.js b/extension/generate_manifest.js index 12327a6..697123a 100644 --- a/extension/generate_manifest.js +++ b/extension/generate_manifest.js @@ -69,6 +69,7 @@ export function generateManifest({ // TODO make permissions literate + // keep in sync with readme const permissions = [ // for keeping extension settings "storage", @@ -90,11 +91,8 @@ export function generateManifest({ if (v3) { if (target === T.CHROME) { // webext lint will warn about this since it's not supported in firefox yet + // see https://github.com/mozilla/web-ext/issues/2916 background['service_worker'] = 'background.js' - - // this isn't supported in chrome manifest v3 (chrome warns about unsupported field) - // but without it webext lint fails - background['scripts'] = ['background.js'] } else { background['scripts'] = ['background.js'] } @@ -102,7 +100,7 @@ export function generateManifest({ background['scripts'] = ['background.js'] background['persistent'] = false } - background['type'] = 'module' // hmm seems like it works in firefox v2 too now?? + background['type'] = 'module' const content_scripts = [] @@ -169,6 +167,9 @@ export function generateManifest({ manifest['browser_specific_settings'] = { 'gecko': { 'id': gecko_id, + 'data_collection_permissions': { // required for new firefox addons + 'required': ['none'] + }, }, } } diff --git a/extension/package.json b/extension/package.json index 519574d..fbed393 100644 --- a/extension/package.json +++ b/extension/package.json @@ -37,15 +37,15 @@ "@types/webextension-polyfill": "^0.12.4", "chrome-webstore-upload-cli": "^3.1.0", "eslint": "^9.39.2", - "globals": "^16.5.0", + "globals": "^17.0.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.2.0", "jest-fetch-mock": "^3.0.3", - "rollup": "^4.18.0", + "rollup": "^4.53.5", "rollup-plugin-copy": "^3.5.0", "tslib": "^2.8.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.50.0", + "typescript-eslint": "^8.51.0", "web-ext": "^9.2.0", "webextension-polyfill": "^0.12.0" }, diff --git a/extension/rollup.config.js b/extension/rollup.config.js index ae3c30a..dcdeda3 100644 --- a/extension/rollup.config.js +++ b/extension/rollup.config.js @@ -1,6 +1,5 @@ import assert from 'assert' import fs from 'fs' -const { globSync } = import('node:fs') import path from 'path' import { fileURLToPath } from 'url' @@ -78,6 +77,8 @@ const compile = inputs => { return { // format: 'esm', // default?? // format: 'iife', // inlines? e.g. could use for bg page if we disable splitting.. + chunkFileNames: '[name].js', // instead of '[name]-[hash].js' -- otherwise the emitted filenames change every time, very annoying for tracking in git + // huh! so if I build all files in one go, it figures out the shared files properly it seems // however it still inlines webextension stuff into one of the files? e.g. common manualChunks: id => { // ugh, seems a bit shit?