Skip to content

🧪 Test & implement executeBatchPayload fallback recovery#14

Closed
hashexplaindata wants to merge 1 commit intomasterfrom
fix/telemetry-fallback-tests-13120139074774822868
Closed

🧪 Test & implement executeBatchPayload fallback recovery#14
hashexplaindata wants to merge 1 commit intomasterfrom
fix/telemetry-fallback-tests-13120139074774822868

Conversation

@hashexplaindata
Copy link
Owner

🎯 What: The executeBatchPayload error handling was missing robust testing and the localStorage fallback mechanism was only a comment placeholder. Furthermore, error messages were logged to the console, violating the silent client directive, and using innerHTML to render error objects posed an XSS risk.

📊 Coverage:

  • A new Playwright Python script now completely tests the end-to-end user flow.
  • A simulated network collapse accurately verifies the failure path of executeBatchPayload.
  • Assertions strictly verify that data is preserved in localStorage under the exact schema needed.
  • Assertions verify that zero errors or warnings leak into the browser console.
  • Assertions verify the precise deception text rendered to the participant.

Result: A robust, tested, and secure fallback recovery mechanism for telemetry data, adhering perfectly to the architectural directives requiring vanilla JS client code tested via external DOM manipulation and network interception. Test coverage for the critical error path is now established.


PR created automatically by Jules for task 13120139074774822868 started by @hashexplaindata

- Added `localStorage` fallback to save `STATE.results` if `executeBatchPayload` fails.
- Key is dynamically generated using `telemetry_backup_${STATE.pid}`.
- Removed `console.error` and other logging for strict silent client mandate.
- Updated DOM status with `textContent` instead of `innerHTML` to prevent XSS.
- Created Playwright script (`telemetry_verification/verify_payload_fallback.py`) to verify the offline failure path, UI updates, `localStorage` state, and lack of console errors.

Co-authored-by: hashexplaindata <221828969+hashexplaindata@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings March 12, 2026 20:24
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the resilience and testability of telemetry data handling. It introduces a localStorage fallback for executeBatchPayload when network or Firebase issues prevent immediate data synchronization, ensuring that critical data is not lost. Concurrently, a new Playwright test suite has been added to thoroughly validate this fallback mechanism, simulating real-world network failures and verifying data integrity and user experience. The changes also improve security and adhere to silent client directives by removing sensitive console logs and mitigating an XSS risk.

Highlights

  • Robust Fallback Mechanism: Implemented a localStorage fallback for executeBatchPayload failures, ensuring telemetry data persistence during network or Firebase unavailability.
  • Comprehensive End-to-End Testing: Introduced a new Playwright Python script (verify_payload_fallback.py) to thoroughly validate the fallback logic, including simulating network failures, verifying data integrity in localStorage, and checking for console silence.
  • Silent Client Adherence: Eliminated all console logging of errors and warnings from the client-side experiment.js to align with architectural directives for silent client behavior.
  • XSS Vulnerability Mitigation: Addressed a potential Cross-Site Scripting (XSS) risk by switching from innerHTML to textContent when updating the sync status message in the DOM, preventing malicious script injection.
Changelog
  • code/experiment.js
    • Removed an unnecessary console log statement during initialization.
    • Modified the executeBatchPayload function to explicitly throw an error when Firebase is not initialized, ensuring the error handling path is consistently triggered.
    • Implemented localStorage as a fallback mechanism to store telemetry data when executeBatchPayload encounters an error, with silent failure for localStorage unavailability.
    • Updated the DOM element for sync status to use textContent instead of innerHTML and provided a user-friendly message indicating a network timeout.
  • telemetry_verification/verify_payload_fallback.py
    • Added a new Playwright Python script to perform end-to-end testing of the executeBatchPayload fallback mechanism.
    • Included steps to simulate user interaction, set the browser context offline to trigger network errors, and finalize the experiment.
    • Implemented assertions to verify the displayed sync status message in the DOM, the successful backup and content of data in localStorage, and the absence of console errors or warnings.
Activity
  • The pull request was created automatically by Jules for task 13120139074774822868, initiated by @hashexplaindata. No other human activity has been recorded yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request successfully implements a robust fallback mechanism for telemetry data using localStorage when the primary sync fails. The changes in experiment.js are excellent, particularly the use of textContent to prevent XSS vulnerabilities and the silent failure handling for localStorage. A comprehensive Playwright test has been added to verify this new fallback logic. My review focuses on improving the robustness of this new test script by replacing fixed sleep intervals with more reliable waiting strategies, which will help prevent flaky tests.

async def run_test():
server = subprocess.Popen(["python3", "-m", "http.server", "8000"])
# Wait for the server to start
time.sleep(2)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a fixed time.sleep() to wait for the server to start can lead to flaky tests. If the server takes longer to start on a slower machine or under load, the test will fail. A more robust approach is to implement a polling mechanism that repeatedly tries to connect to the server until it's responsive before proceeding with the test.

Comment on lines +36 to +39
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a fixed asyncio.sleep() within the loop can lead to flaky tests. A more robust approach is to use Playwright's auto-waiting capabilities with expect. By waiting for the trial counter to display the correct number for the current trial, you ensure the test only proceeds when the UI is actually ready for the next interaction. This avoids arbitrary wait times and makes the test more reliable. You'll need to add from playwright.async_api import expect at the top of the file.

Suggested change
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")
for i in range(6):
# Wait for the trial counter to update, indicating the trial is ready.
await expect(page.locator("#trial-counter")).to_have_text(f"Diagnostic {i + 1}/6")
# Click the first card
await page.click(".bento-choice-card:first-child")

Comment on lines +53 to +58
await asyncio.sleep(1) # wait for the failure catch block to execute

print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a fixed asyncio.sleep() to wait for an asynchronous operation can make tests flaky. It's better to use Playwright's built-in waiting mechanisms. You can replace the sleep and the manual assertion with a single expect() call that waits for the element's text to match the expected value. This makes the test more reliable and concise. You'll need to add from playwright.async_api import expect at the top of the file if you haven't already.

Suggested change
await asyncio.sleep(1) # wait for the failure catch block to execute
print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."
print("Verifying text content...")
# Wait for the DOM to update with the failure message and assert its content.
await expect(page.locator("#sync-status")).to_have_text("Diagnostic Complete. A network timeout occurred. You may safely close this tab.")

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a real executeBatchPayload failure fallback by persisting telemetry to localStorage and rendering a safe, non-innerHTML status message, plus adds an end-to-end Playwright (Python) verification script for the offline/error path.

Changes:

  • Implement executeBatchPayload fallback recovery by saving STATE.results to localStorage on sync failure and rendering a fixed deception message via textContent.
  • Remove console logging in the client to comply with the silent-client directive.
  • Add a Playwright async Python script that drives the UI, simulates offline mode, and asserts DOM text + localStorage backup + console silence.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.

File Description
telemetry_verification/verify_payload_fallback.py Adds Playwright E2E verification of the fallback flow (offline simulation, DOM text assertion, localStorage schema checks, console silence checks).
code/experiment.js Implements localStorage fallback in executeBatchPayload, removes console logging, and replaces error rendering with textContent.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +19 to +85
console_messages = []

# Listen to console events
page.on("console", lambda msg: console_messages.append(msg.text))

print("Navigating to the experiment...")
await page.goto("http://localhost:8000/code/index.html?condition=control")

print("Starting the trials...")
# Screen 1 -> 2
await page.click("#btn-consent")

# Screen 2 -> trial
await page.click(".btn-familiarity[data-val='2']")

print("Completing trials...")
# 6 Trials
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")

print("Providing justification...")
# Screen 9 (Justification)
await page.fill("#semantic-justification", "This is a valid semantic justification.")

print("Simulating offline environment...")
# Set context to offline before clicking finalize to simulate network error for Firebase
await context.set_offline(True)

print("Finalizing...")
# Click Finalize -> Screen 10 (executeBatchPayload)
await page.click("#btn-finalize")

await asyncio.sleep(1) # wait for the failure catch block to execute

print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."

print("Verifying localStorage backup...")
# Check localStorage
local_storage = await page.evaluate("() => Object.entries(localStorage)")

backup_key = None
backup_value = None
for key, val in local_storage:
if key.startswith("telemetry_backup_"):
backup_key = key
backup_value = val
break

assert backup_key is not None, "Backup key not found in localStorage"

# Verify backup content
data = json.loads(backup_value)
assert len(data) == 6, f"Expected 6 rows in Tidy Data schema, got {len(data)}"
assert "semantic_justification" in data[0], "Semantic justification not appended."
assert data[0]["semantic_justification"] == "This is a valid semantic justification.", "Semantic justification incorrect."

print("Verifying console silence...")
# Verify console
# Ignore the 404 for firebase-config.js as it's missing from repo
# We are verifying that experiment.js doesn't log errors
errors_or_warnings = [msg for msg in console_messages if ("error" in msg.lower() or "warn" in msg.lower()) and "404" not in msg]
assert len(errors_or_warnings) == 0, f"Found console errors/warnings: {errors_or_warnings}"
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Console-silence validation is based on substring matching against msg.text, but Playwright console entries have a severity (msg.type) and not all errors/warnings necessarily include the words "error"/"warn" in their text. Consider recording (msg.type, msg.text) and asserting no type in {"warning","error"}, and also listen to page.on("pageerror", ...) to catch uncaught exceptions.

Copilot uses AI. Check for mistakes.
Comment on lines +475 to 482
// Fallback: Save to localStorage for later recovery
try {
localStorage.setItem(`telemetry_backup_${STATE.pid}`, JSON.stringify(STATE.results));
} catch (storageError) {
// Silently fail if localStorage is unavailable
}
DOM.syncStatus.textContent = "Diagnostic Complete. A network timeout occurred. You may safely close this tab.";
}
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the fallback path, data is stored under telemetry_backup_${STATE.pid}, but the UI never reveals the PID (and onSyncSuccess() is not called), making it hard to recover a specific participant’s backup key. Consider also displaying the PID/recovery key when the localStorage write succeeds, or storing under a stable key plus PID inside the value.

Copilot uses AI. Check for mistakes.
print(f"Test Failed: {e}")
sys.exit(1)
finally:
server.terminate()
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

server.terminate() is called but the process is never wait()ed; this can leave a zombie process (especially on CI) and keep port 8000 occupied. Consider terminate() + wait(timeout=...), and fall back to kill() if needed.

Suggested change
server.terminate()
if server.poll() is None:
server.terminate()
try:
server.wait(timeout=5)
except subprocess.TimeoutExpired:
server.kill()
server.wait()

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +85
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."

print("Verifying localStorage backup...")
# Check localStorage
local_storage = await page.evaluate("() => Object.entries(localStorage)")

backup_key = None
backup_value = None
for key, val in local_storage:
if key.startswith("telemetry_backup_"):
backup_key = key
backup_value = val
break

assert backup_key is not None, "Backup key not found in localStorage"

# Verify backup content
data = json.loads(backup_value)
assert len(data) == 6, f"Expected 6 rows in Tidy Data schema, got {len(data)}"
assert "semantic_justification" in data[0], "Semantic justification not appended."
assert data[0]["semantic_justification"] == "This is a valid semantic justification.", "Semantic justification incorrect."

print("Verifying console silence...")
# Verify console
# Ignore the 404 for firebase-config.js as it's missing from repo
# We are verifying that experiment.js doesn't log errors
errors_or_warnings = [msg for msg in console_messages if ("error" in msg.lower() or "warn" in msg.lower()) and "404" not in msg]
assert len(errors_or_warnings) == 0, f"Found console errors/warnings: {errors_or_warnings}"
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script uses bare assert statements for test validation; running Python with optimizations (python -O) disables asserts and would let failures pass silently. Prefer explicit checks that raise/exit (or use a test framework like pytest) so the validations always execute.

Copilot uses AI. Check for mistakes.
async def run_test():
server = subprocess.Popen(["python3", "-m", "http.server", "8000"])
# Wait for the server to start
time.sleep(2)
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

time.sleep(2) is called inside an async def and blocks the event loop, which can make the Playwright run flaky/slow. Prefer await asyncio.sleep(...) or (better) actively poll for the server port to accept connections before proceeding.

Suggested change
time.sleep(2)
await asyncio.sleep(2)

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +25
server = subprocess.Popen(["python3", "-m", "http.server", "8000"])
# Wait for the server to start
time.sleep(2)

try:
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()

console_messages = []

# Listen to console events
page.on("console", lambda msg: console_messages.append(msg.text))

print("Navigating to the experiment...")
await page.goto("http://localhost:8000/code/index.html?condition=control")
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The http.server is started without setting cwd, so if this script is executed from a directory other than the repo root (e.g. telemetry_verification/), GET /code/index.html will 404 and the test will fail. Consider resolving the repo root via __file__ and starting the server with cwd=... (or serve an explicit directory).

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +87
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()

console_messages = []

# Listen to console events
page.on("console", lambda msg: console_messages.append(msg.text))

print("Navigating to the experiment...")
await page.goto("http://localhost:8000/code/index.html?condition=control")

print("Starting the trials...")
# Screen 1 -> 2
await page.click("#btn-consent")

# Screen 2 -> trial
await page.click(".btn-familiarity[data-val='2']")

print("Completing trials...")
# 6 Trials
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")

print("Providing justification...")
# Screen 9 (Justification)
await page.fill("#semantic-justification", "This is a valid semantic justification.")

print("Simulating offline environment...")
# Set context to offline before clicking finalize to simulate network error for Firebase
await context.set_offline(True)

print("Finalizing...")
# Click Finalize -> Screen 10 (executeBatchPayload)
await page.click("#btn-finalize")

await asyncio.sleep(1) # wait for the failure catch block to execute

print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."

print("Verifying localStorage backup...")
# Check localStorage
local_storage = await page.evaluate("() => Object.entries(localStorage)")

backup_key = None
backup_value = None
for key, val in local_storage:
if key.startswith("telemetry_backup_"):
backup_key = key
backup_value = val
break

assert backup_key is not None, "Backup key not found in localStorage"

# Verify backup content
data = json.loads(backup_value)
assert len(data) == 6, f"Expected 6 rows in Tidy Data schema, got {len(data)}"
assert "semantic_justification" in data[0], "Semantic justification not appended."
assert data[0]["semantic_justification"] == "This is a valid semantic justification.", "Semantic justification incorrect."

print("Verifying console silence...")
# Verify console
# Ignore the 404 for firebase-config.js as it's missing from repo
# We are verifying that experiment.js doesn't log errors
errors_or_warnings = [msg for msg in console_messages if ("error" in msg.lower() or "warn" in msg.lower()) and "404" not in msg]
assert len(errors_or_warnings) == 0, f"Found console errors/warnings: {errors_or_warnings}"

print("All assertions passed. Fallback logic verified successfully.")
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

browser/context are never explicitly closed. If an assertion fails before process teardown, Playwright can leave Chromium processes running and make subsequent runs unstable. Use async with p.chromium.launch(...) as browser: / async with browser.new_context() as context: or close them in finally.

Suggested change
browser = await p.chromium.launch(headless=True)
context = await browser.new_context()
page = await context.new_page()
console_messages = []
# Listen to console events
page.on("console", lambda msg: console_messages.append(msg.text))
print("Navigating to the experiment...")
await page.goto("http://localhost:8000/code/index.html?condition=control")
print("Starting the trials...")
# Screen 1 -> 2
await page.click("#btn-consent")
# Screen 2 -> trial
await page.click(".btn-familiarity[data-val='2']")
print("Completing trials...")
# 6 Trials
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")
print("Providing justification...")
# Screen 9 (Justification)
await page.fill("#semantic-justification", "This is a valid semantic justification.")
print("Simulating offline environment...")
# Set context to offline before clicking finalize to simulate network error for Firebase
await context.set_offline(True)
print("Finalizing...")
# Click Finalize -> Screen 10 (executeBatchPayload)
await page.click("#btn-finalize")
await asyncio.sleep(1) # wait for the failure catch block to execute
print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."
print("Verifying localStorage backup...")
# Check localStorage
local_storage = await page.evaluate("() => Object.entries(localStorage)")
backup_key = None
backup_value = None
for key, val in local_storage:
if key.startswith("telemetry_backup_"):
backup_key = key
backup_value = val
break
assert backup_key is not None, "Backup key not found in localStorage"
# Verify backup content
data = json.loads(backup_value)
assert len(data) == 6, f"Expected 6 rows in Tidy Data schema, got {len(data)}"
assert "semantic_justification" in data[0], "Semantic justification not appended."
assert data[0]["semantic_justification"] == "This is a valid semantic justification.", "Semantic justification incorrect."
print("Verifying console silence...")
# Verify console
# Ignore the 404 for firebase-config.js as it's missing from repo
# We are verifying that experiment.js doesn't log errors
errors_or_warnings = [msg for msg in console_messages if ("error" in msg.lower() or "warn" in msg.lower()) and "404" not in msg]
assert len(errors_or_warnings) == 0, f"Found console errors/warnings: {errors_or_warnings}"
print("All assertions passed. Fallback logic verified successfully.")
async with p.chromium.launch(headless=True) as browser:
async with browser.new_context() as context:
page = await context.new_page()
console_messages = []
# Listen to console events
page.on("console", lambda msg: console_messages.append(msg.text))
print("Navigating to the experiment...")
await page.goto("http://localhost:8000/code/index.html?condition=control")
print("Starting the trials...")
# Screen 1 -> 2
await page.click("#btn-consent")
# Screen 2 -> trial
await page.click(".btn-familiarity[data-val='2']")
print("Completing trials...")
# 6 Trials
for i in range(6):
await asyncio.sleep(0.5) # Wait for debounce and DOM updates
# Click the first card
await page.click(".bento-choice-card:first-child")
print("Providing justification...")
# Screen 9 (Justification)
await page.fill("#semantic-justification", "This is a valid semantic justification.")
print("Simulating offline environment...")
# Set context to offline before clicking finalize to simulate network error for Firebase
await context.set_offline(True)
print("Finalizing...")
# Click Finalize -> Screen 10 (executeBatchPayload)
await page.click("#btn-finalize")
await asyncio.sleep(1) # wait for the failure catch block to execute
print("Verifying text content...")
# Check DOM update
sync_status_text = await page.inner_text("#sync-status")
assert "Diagnostic Complete. A network timeout occurred. You may safely close this tab." in sync_status_text, "Status text does not match expected deception message."
print("Verifying localStorage backup...")
# Check localStorage
local_storage = await page.evaluate("() => Object.entries(localStorage)")
backup_key = None
backup_value = None
for key, val in local_storage:
if key.startswith("telemetry_backup_"):
backup_key = key
backup_value = val
break
assert backup_key is not None, "Backup key not found in localStorage"
# Verify backup content
data = json.loads(backup_value)
assert len(data) == 6, f"Expected 6 rows in Tidy Data schema, got {len(data)}"
assert "semantic_justification" in data[0], "Semantic justification not appended."
assert data[0]["semantic_justification"] == "This is a valid semantic justification.", "Semantic justification incorrect."
print("Verifying console silence...")
# Verify console
# Ignore the 404 for firebase-config.js as it's missing from repo
# We are verifying that experiment.js doesn't log errors
errors_or_warnings = [msg for msg in console_messages if ("error" in msg.lower() or "warn" in msg.lower()) and "404" not in msg]
assert len(errors_or_warnings) == 0, f"Found console errors/warnings: {errors_or_warnings}"
print("All assertions passed. Fallback logic verified successfully.")

Copilot uses AI. Check for mistakes.
@hashexplaindata
Copy link
Owner Author

Closing: Fixes have already been incorporated directly into the master branch.

@google-labs-jules
Copy link
Contributor

Closing: Fixes have already been incorporated directly into the master branch.

Understood. Acknowledging that this work is now obsolete and stopping work on this task.

@hashexplaindata hashexplaindata deleted the fix/telemetry-fallback-tests-13120139074774822868 branch March 12, 2026 23:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants