diff --git a/README.md b/README.md
index 7f2fe2f..e76a92a 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,18 @@
-# Serenade Chrome Extension
+# WebChime Chrome Extension
+Available for download on the [chrome web store](https://chromewebstore.google.com/detail/pfcnjhdacgnclpladnjfpepigbbbbkfc?utm_source=item-share-cb)
+
-Source for the [Serenade Chrome Extension](https://chrome.google.com/webstore/detail/serenade-for-chrome/bgfbijeikimjmdjldemlegooghdjinmj?hl=en)
+## Changelog
+- v 2.0.5, merged 9/9/2025, confirmed chrome version Version 140.0.7339.133 (Official Build) (arm64). Adds trusted types compatibility for google suite apps (and other that might use this feature) See [docs](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API)
+
+## Alscotty hotfix 8/21/2025
+- refactored to make it more compliant with manifest_v3, extension what hitting issues with injecting the content scripts, thus it was not executing commands like clicks/typing, particularly "show links" or "show inputs" were loading overlay but unable to actually click
+- now it is working again! tested on chrome
+- the tests need some updating (as the show fails for content script still, inaccurate) but the playwright stuff is a good start to test for regressions etc.
## Installation
-1. Download `build.zip` and unzip
+1. Run `npm run build && npm run dist` to generate `build.zip`, then unzip
2. In Chrome, go to [chrome://extensions](chrome://extensions) and enable Developer Mode
3. Click "Load unpacked" and select the unzipped `build` folder
@@ -21,7 +29,7 @@ Each of these script types can communicate between each other with browser event
- Extension code
- `extension.ts`: Entry-point for the extension
- - `ipc.ts`: Handles communication between the Serenade app and the Chrome extension. Also determines which command handler to send each incoming command to.
+ - `ipc.ts`: Handles communication between the Serenade app and WebChime Chrome extension. Also determines which command handler to send each incoming command to.
- `extension-command-handler.ts`: Handles commands that do not need page content/require access to the browser APIs (e.g. tab management)
- Content scripts
- `content-script.ts`: Adds the tag containing the injected scripts and sets up communication between `ipc.ts` and the injected code
@@ -69,3 +77,6 @@ The `Editor` class also contains some helper functions to determine a suitable f
1. Update the version number in `manifest.json`
2. Run `npm run dist`
3. Upload the new `build.zip` file to the Chrome Web Store
+
+## Privacy Policy
+[see link](https://webchime-privacy-fir-6d7s.bolt.host)
diff --git a/build.zip b/build.zip
deleted file mode 100644
index 8244fff..0000000
Binary files a/build.zip and /dev/null differ
diff --git a/extension_promo_images/example_img_1.png b/extension_promo_images/example_img_1.png
new file mode 100644
index 0000000..4c7a411
Binary files /dev/null and b/extension_promo_images/example_img_1.png differ
diff --git a/extension_promo_images/webchime.png b/extension_promo_images/webchime.png
new file mode 100644
index 0000000..ca54a0d
Binary files /dev/null and b/extension_promo_images/webchime.png differ
diff --git a/manifest.json b/manifest.json
index e3c1e13..4959b73 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,12 +1,12 @@
{
"manifest_version": 3,
- "name": "Serenade",
- "version": "2.0.3",
- "description": "Code with voice. Learn more at https://serenade.ai.",
+ "name": "WebChime",
+ "version": "2.0.5",
+ "description": "Browse the web hands-free with only your voice. This extension pairs with Serenade AI https://serenade.ai.",
"permissions": ["background", "tabs", "storage", "alarms", "idle", "scripting"],
"host_permissions": ["*://*/*"],
"action": {
- "default_title": "Serenade for Chrome",
+ "default_title": "WebChime for Chrome",
"default_popup": "build/popup.html"
},
"background": {
diff --git a/package.json b/package.json
index a3b19e6..4548683 100644
--- a/package.json
+++ b/package.json
@@ -18,11 +18,19 @@
"build": "webpack --mode development",
"dist": "webpack --mode production && zip -r build.zip build img manifest.json",
"watch": "webpack --mode development --watch",
- "test": "open http://localhost:8001/src/test & python -m http.server 8001"
+ "test": "open http://localhost:8001/src/test & python3 -m http.server 8001",
+ "test:playwright": "npm run build && playwright test",
+ "test:watch": "node scripts/watch-and-test.js",
+ "test:ui": "npm run build && playwright test --ui",
+ "test:debug": "npm run build && playwright test --debug"
},
"devDependencies": {
+ "@playwright/test": "^1.40.0",
"@types/chrome": "^0.0.174",
"@types/uuid": "^8.3.3",
+ "chokidar": "^3.5.3",
+ "concurrently": "^8.2.2",
+ "lodash.debounce": "^4.0.8",
"copy-webpack-plugin": "^11.0.0",
"ts-loader": "^9.2.6",
"typescript": "^4.5.4",
diff --git a/playwright-report/index.html b/playwright-report/index.html
new file mode 100644
index 0000000..1cc931c
--- /dev/null
+++ b/playwright-report/index.html
@@ -0,0 +1,76 @@
+
+
+
+
+
');
+ await page.waitForTimeout(2000);
+
+ // Test undo command - this is a smoke test
+ const undoResult = await page.evaluate(() => {
+ return new Promise((resolve) => {
+ const id = Math.random();
+ const responseHandler = (e: any) => {
+ if (e.detail.id === id) {
+ document.removeEventListener('serenade-injected-script-command-response', responseHandler);
+ resolve({ success: true, data: e.detail });
+ }
+ };
+ document.addEventListener('serenade-injected-script-command-response', responseHandler);
+
+ setTimeout(() => {
+ resolve({ success: false, error: 'timeout' });
+ }, 1000);
+
+ document.dispatchEvent(
+ new CustomEvent('serenade-injected-script-command-request', {
+ detail: { id, data: { type: 'COMMAND_TYPE_UNDO' } },
+ })
+ );
+ });
+ });
+
+ console.log('Undo command result:', undoResult);
+ });
+
+});
\ No newline at end of file
diff --git a/tests/extension-integration.spec.ts b/tests/extension-integration.spec.ts
new file mode 100644
index 0000000..f44e3d2
--- /dev/null
+++ b/tests/extension-integration.spec.ts
@@ -0,0 +1,67 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Serenade Extension Integration Tests', () => {
+ test('extension service worker is running', async ({ context }) => {
+ // Get the service workers
+ const serviceWorkers = context.serviceWorkers();
+ const serviceWorker = serviceWorkers.find(sw => sw.url().includes('extension.js'));
+
+ if (!serviceWorker) {
+ console.log('Service worker not found, extension may not be loaded properly');
+ return;
+ }
+
+ // Service worker should be loaded
+ expect(serviceWorker).toBeTruthy();
+
+ // Check if IPC is initialized (basic smoke test)
+ const hasIPC = await serviceWorker.evaluate(() => {
+ return typeof (globalThis as any).ipc !== 'undefined';
+ });
+
+ // This might be undefined if IPC isn't exposed globally, which is okay
+ console.log('IPC available globally:', hasIPC);
+ });
+
+ test('extension responds to tab changes', async ({ context, page }) => {
+ await page.goto('data:text/html,Test
Extension Test Page
Test Link 1Test Link 2');
+ await page.waitForTimeout(1000);
+
+ // Create a new tab
+ const newPage = await context.newPage();
+ await newPage.goto('https://google.com');
+ await newPage.waitForTimeout(1000);
+
+ // Extension should handle tab activation
+ // This is more of a smoke test since we can't easily mock the WebSocket connection
+
+ await newPage.close();
+ });
+
+ test('extension handles chrome:// pages gracefully', async ({ context }) => {
+ // Try to navigate to a chrome:// page
+ try {
+ const page = await context.newPage();
+ await page.goto('chrome://extensions/');
+
+ // Extension should not crash on chrome pages
+ // Wait a bit to see if any errors occur
+ await page.waitForTimeout(2000);
+
+ // If we get here without throwing, the extension handled it gracefully
+ expect(true).toBe(true);
+
+ await page.close();
+ } catch (error) {
+ // Some chrome:// pages might not be accessible in tests, which is fine
+ console.log('Chrome pages test skipped:', error);
+ }
+ });
+
+
+
+
+
+
+
+});
\ No newline at end of file
diff --git a/tests/global-setup.ts b/tests/global-setup.ts
new file mode 100644
index 0000000..4135ec5
--- /dev/null
+++ b/tests/global-setup.ts
@@ -0,0 +1,37 @@
+import { chromium, FullConfig } from '@playwright/test';
+import * as path from 'path';
+import { execSync } from 'child_process';
+import * as fs from 'fs';
+
+async function globalSetup(config: FullConfig) {
+ // Build the extension first if build directory doesn't exist
+ const buildPath = path.join(process.cwd(), 'tests/../build');
+
+ if (!fs.existsSync(buildPath)) {
+ console.log('Building extension...');
+ execSync('npm run build', { cwd: process.cwd(), stdio: 'inherit' });
+ }
+
+ // Launch browser with extension to verify it loads correctly
+ const browser = await chromium.launch({
+ headless: true,
+ args: [
+ `--disable-extensions-except=${buildPath}`,
+ `--load-extension=${buildPath}`,
+ '--no-first-run',
+ ],
+ });
+
+ const context = await browser.newContext();
+ const page = await context.newPage();
+
+ // Wait for extension to be ready - use data URL instead of external site
+ await page.goto('data:text/html,Test
Extension Test Page
');
+ await page.waitForTimeout(1000); // Give extension time to initialize
+
+ console.log('Extension loaded successfully');
+
+ await browser.close();
+}
+
+export default globalSetup;
\ No newline at end of file
diff --git a/tests/overlay-functionality.spec.ts b/tests/overlay-functionality.spec.ts
new file mode 100644
index 0000000..271ff3e
--- /dev/null
+++ b/tests/overlay-functionality.spec.ts
@@ -0,0 +1,297 @@
+import { test, expect } from '@playwright/test';
+
+test.describe('Serenade Extension Overlay Tests', () => {
+ test.beforeEach(async ({ page }) => {
+ // Go to a page with links for testing
+ await page.goto('data:text/html,Test
Extension Test Page
Test Link 1Test Link 2');
+ await page.waitForTimeout(2000); // Wait for extension to initialize
+ });
+
+ test('show links command creates overlays', async ({ page }) => {
+ // Send command to show links
+ const commandResult = await page.evaluate(() => {
+ return new Promise((resolve) => {
+ const id = Math.random();
+ let resolved = false;
+
+ const responseHandler = (e: any) => {
+ if (e.detail.id === id && !resolved) {
+ resolved = true;
+ document.removeEventListener('serenade-injected-script-command-response', responseHandler);
+ resolve({ success: true, data: e.detail });
+ }
+ };
+
+ document.addEventListener('serenade-injected-script-command-response', responseHandler);
+
+ setTimeout(() => {
+ if (!resolved) {
+ resolved = true;
+ document.removeEventListener('serenade-injected-script-command-response', responseHandler);
+ resolve({ success: false, error: 'timeout' });
+ }
+ }, 1000);
+
+ document.dispatchEvent(
+ new CustomEvent('serenade-injected-script-command-request', {
+ detail: {
+ id,
+ data: { type: 'COMMAND_TYPE_SHOW', text: 'links' },
+ },
+ })
+ );
+ });
+ });
+
+ console.log('Show links command result:', commandResult);
+
+ // Wait for overlays to appear
+ await page.waitForTimeout(1000);
+
+ // Check if overlays were created
+ const overlays = await page.locator('.serenade-overlay').count();
+ console.log('Links overlays found:', overlays);
+
+ // Since extension communication might not work in test environment,
+ // we'll just verify the command was sent without error
+ expect(commandResult).toBeDefined();
+ });
+
+
+
+ test('clear overlays command works', async ({ page }) => {
+ // First show links to create overlays
+ await page.evaluate(async () => {
+ const id = Math.random();
+ const responseHandler = (e: any) => {
+ if (e.detail.id === id) {
+ document.removeEventListener('serenade-injected-script-command-response', responseHandler);
+ }
+ };
+ document.addEventListener('serenade-injected-script-command-response', responseHandler);
+ document.dispatchEvent(
+ new CustomEvent('serenade-injected-script-command-request', {
+ detail: { id, data: { type: 'COMMAND_TYPE_SHOW', text: 'links' } },
+ })
+ );
+ });
+
+ await page.waitForTimeout(1000);
+
+ // Now clear overlays
+ await page.evaluate(async () => {
+ const id = Math.random();
+ const responseHandler = (e: any) => {
+ if (e.detail.id === id) {
+ document.removeEventListener('serenade-injected-script-command-response', responseHandler);
+ }
+ };
+ document.addEventListener('serenade-injected-script-command-response', responseHandler);
+ document.dispatchEvent(
+ new CustomEvent('serenade-injected-script-command-request', {
+ detail: { id, data: { type: 'COMMAND_TYPE_CANCEL' } },
+ })
+ );
+ });
+
+ await page.waitForTimeout(500);
+
+ // Overlays should be gone
+ const overlaysAfterCancel = await page.locator('.serenade-overlay').count();
+ expect(overlaysAfterCancel).toBe(0);
+ });
+
+ test('show inputs command creates overlays for form elements', async ({ page }) => {
+ await page.goto('data:text/html,Test