Skip to content

12789/feat/AB Testing#12788

Open
Sadashii wants to merge 11 commits into
internetarchive:masterfrom
Sadashii:feat/ab-testing-architecture
Open

12789/feat/AB Testing#12788
Sadashii wants to merge 11 commits into
internetarchive:masterfrom
Sadashii:feat/ab-testing-architecture

Conversation

@Sadashii
Copy link
Copy Markdown
Collaborator

@Sadashii Sadashii commented May 21, 2026

Closes #12789

feat - This PR adds a deterministic client-and-server side A/B testing architecture.

Technical

  • Deterministic Hashing: User variant allocation is computed dynamically using an MD5 hash of the experiment name concatenated with the user's session identifier (cookie) or IP address fallback. This removes any database dependency or network overhead for bucketing.
  • Unified Backend State:
    • For web.py, the ExperimentsProcessor populates web.ctx["experiments"] and infogami.utils.context.context["experiments"] (making variables directly accessible to Templetor templates/macros).
    • For FastAPI, the ABTestingMiddleware registers variants on the ASGI request.state.experiments.
  • Global JS Availability: Experiments are serialized to the global client scope (window.OL_EXPERIMENTS) during page load. The helper getExperiment is exposed on window and as an ES module for Vue, Lit, and jQuery components to consume synchronously.
  • URL Overrides: One can override variant assignment on demand via query parameters in the ?experiment_[experiment_name]=[variant_name] format.

Testing

  1. Run Backend Unit Tests:
    docker compose run --rm home pytest openlibrary/tests/core/test_experiments.py
  2. Verify URL Overrides:
    • Navigate to the homepage with an override parameter: http://localhost:8080/?experiment_AB_Testing=b
    • Open your browser's developer console and inspect window.OL_EXPERIMENTS (or call window.getExperiment('AB_Testing')) to verify it matches your query override.

Screenshot

No visual UI updates are introduced; this is a behavioral framework change. Manual verification of the output via browser developer console:

> window.OL_EXPERIMENTS
< { AB_Testing: "b" }

Stakeholders

Copilot AI review requested due to automatic review settings May 21, 2026 20:10
@mekarpeles mekarpeles added the Needs: Submitter Input Waiting on input from the creator of the issue/pr [managed] label May 21, 2026
@mekarpeles
Copy link
Copy Markdown
Member

Thank you for this contribution, @Sadashii!

Copilot has been assigned for an initial review.

A reviewer must first be assigned to this PR. There are currently 71 open PRs of equal or higher priority ahead of yours in the queue.

Possible improvements for this PR

  • No linked issue: This PR introduces a new feature but does not reference a GitHub issue. Adding a #NNN reference to an existing issue (or opening one) helps maintainers trace the motivation, confirm roadmap alignment, and prioritize review.
PR triage checklist (maintainers / Pam)
  • PR description — not empty; explains what the change does and how to verify it
  • References an issue — PR body contains a #NNN reference
    • Linked issue is triaged — has a Priority: * label (not just Needs: Triage)
    • Linked issue is assigned — has at least one assignee
  • Commit history clean — no WIP/fixup/conflict noise; commit messages are meaningful
  • CI passing — no failing check-runs
  • Test cases present — if the change touches substantive logic, test coverage exists or is explained
  • Proof of testing — PR body includes a description of what was tested, a screenshot, or a video

Note

This comment was automatically generated by Pam, Open Library's Project AI Manager, on behalf of @mekarpeles. Pam is designed to provide status visibility, perform basic project management functions and relevant codebase research, and provide actionable feedback so contributors aren't left waiting.

@mekarpeles mekarpeles added the Priority: 1 Do this week, receiving emails, time sensitive, . [managed] label May 21, 2026
Copy link
Copy Markdown
Contributor

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

Adds a deterministic A/B testing framework that assigns experiment variants consistently on both legacy web.py and FastAPI paths, and exposes the resulting assignments to templates and client-side code.

Changes:

  • Introduces core experiment bucketing + override support (openlibrary/core/experiments.py) with unit tests.
  • Injects evaluated experiments into legacy template context and serializes them into the page head as window.OL_EXPERIMENTS, plus a JS helper (window.getExperiment / ES module).
  • Adds FastAPI middleware to populate request.state.experiments for ASGI requests.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
openlibrary/core/experiments.py New deterministic bucketing + override evaluation for active experiments.
openlibrary/tests/core/test_experiments.py Adds unit tests covering fallback behavior, distribution smoke test, and overrides.
openlibrary/plugins/openlibrary/processors.py Adds a web.py processor to compute experiments and inject into web.ctx and infogami context.
openlibrary/plugins/openlibrary/code.py Registers the new ExperimentsProcessor in the legacy processor chain.
openlibrary/templates/site/head.html Serializes computed experiments into window.OL_EXPERIMENTS during page load.
openlibrary/plugins/openlibrary/js/experiments.js Adds getExperiment() helper and exposes it on window.
openlibrary/plugins/openlibrary/js/index.js Ensures experiments helper is bundled site-wide.
openlibrary/fastapi/middleware/experiments.py Adds FastAPI middleware to compute experiments per request.
openlibrary/asgi_app.py Registers the new FastAPI A/B testing middleware.
openlibrary/i18n/messages.pot Updates POT output (currently appears truncated / incomplete).

Comment thread openlibrary/core/experiments.py Outdated
Comment thread openlibrary/tests/core/test_experiments.py Outdated
Comment thread openlibrary/plugins/openlibrary/processors.py Outdated
Comment thread openlibrary/plugins/openlibrary/code.py Outdated
Comment thread openlibrary/fastapi/middleware/experiments.py Outdated
Comment thread openlibrary/fastapi/middleware/experiments.py Outdated
Sadashii added 2 commits May 22, 2026 02:11
…d improve typing

- Convert ABTestingMiddleware to pure ASGI middleware to reduce BaseHTTPMiddleware overhead.
- Restrict and filter query overrides in both web.py and FastAPI to ignore non-experiment inputs and avoid reading POST bodies.
- Update get_variant and get_user_experiments signatures to support str | None for user_identifier.
- These changes are based on Copilot suggestions.
@github-actions github-actions Bot removed the Needs: Submitter Input Waiting on input from the creator of the issue/pr [managed] label May 21, 2026
@Sadashii Sadashii changed the title Feat/ab testing architecture 12789/feat/AB Testing May 22, 2026
Comment thread openlibrary/plugins/openlibrary/processors.py Outdated
…alidation

- Use built-in verify_session_cookie to prevent cookie spoofing without DB/cache lookup overhead

- Use Literal for audience configuration targeting rules in ExperimentConfig

- Update test cases to dynamically inject ACTIVE_EXPERIMENTS and support Audience logic

- Reformat code to pass all ruff check, ruff format, and mypy pre-commit hooks

Co-authored-by: Copilot Suggestions <support@copilot.github.com>
@Sadashii Sadashii requested a review from Copilot May 28, 2026 18:43
Copy link
Copy Markdown
Contributor

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

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Comment thread openlibrary/plugins/openlibrary/processors.py
Comment thread openlibrary/templates/site/head.html
Comment thread openlibrary/fastapi/middleware/experiments.py Outdated
Comment thread openlibrary/tests/core/test_experiments.py Outdated
Comment thread openlibrary/core/experiments.py
Comment thread openlibrary/plugins/openlibrary/processors.py Outdated
Comment thread openlibrary/plugins/openlibrary/code.py Outdated
Comment thread openlibrary/plugins/openlibrary/code.py Outdated
Comment thread openlibrary/plugins/upstream/utils.py Outdated
Comment on lines +305 to +306
def json_encode(d, indent=0) -> str:
return json.dumps(d, indent=indent)
return json.dumps(d, indent=indent).replace("<", "\\u003c").replace(">", "\\u003e").replace("&", "\\u0026")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems kind of hacky.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Not sure of simpler ways, do you have any recommendations?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: 1 Do this week, receiving emails, time sensitive, . [managed]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AB Testing Architecture

4 participants