From 7cfbce447f4b31c6395b7bfa7d660c3c7f1f5053 Mon Sep 17 00:00:00 2001 From: William Jacoby Date: Fri, 13 Nov 2026 11:57:41 -0600 Subject: [PATCH] Add screenshots.py --seed-board and Restore Jumpbox screenshots screenshots.py can now take an extension's screenshots on an installed seed-forum board (--seed-board, through _board.SeedBoard as feature_checks.py and page_a11y.py already do), so phpBB 4.0-only extensions and boards built from an open phpbb/phpbb pull request can be shot. The board is not reset; only its name and description change for the shots and are put back. shots_restorejumpbox saves the open "Jump to" list on a forum page, a topic page and a phone-width forum page, and the extension's ACP Load settings fieldset. It needs a board built from phpbb/phpbb#6927, for example the one db-matrix.sh -b master -d sqlite -P 6927 -k keeps. Co-Authored-By: Claude Opus 5.5 --- phpbb-test-board/README.md | 9 +++ phpbb-test-board/screenshots.py | 120 +++++++++++++++++++++++++++----- tests/test-phpbb-test-board.sh | 17 +++++ 3 files changed, 127 insertions(+), 19 deletions(-) diff --git a/phpbb-test-board/README.md b/phpbb-test-board/README.md index 89d315c..499b3b1 100644 --- a/phpbb-test-board/README.md +++ b/phpbb-test-board/README.md @@ -145,6 +145,7 @@ Saves PNG screenshots of an extension's or style's pages with [Playwright](https **Options:** - `--seed SCRIPT` runs a PHP script that fills the board with forums, topics and users first, as `php SCRIPT BOARD_ROOT`. Use it for styles and for any pages that need real content; a fresh board has one forum and one post. [seed-forum](https://github.com/phpbbmodders/seed-forum)'s `bin/seed-standard-fixtures.php` works well. - `--build DIR` is for phpbbmodders/documentation only: the phpbbdocs-hugo build to serve. +- `--seed-board BOARD_ROOT` (`-S`) takes an extension's screenshots on an installed [seed-forum](https://github.com/phpbbmodders/seed-forum) board instead, with the extension already enabled on it. Use it for phpBB 4.0-only extensions, or for a board built from an open phpbb/phpbb pull request: `db-matrix.sh -k` builds such a board and keeps it, with the extension mounted. The board is not reset; only its name and description are changed for the screenshots and put back afterwards. Pass the board's own port with `--port`; `--seed` and `--with` don't apply, since seed-forum seeds the board and enables what it needs. `phpbbmodders/contactadmin` saves the three documentation gallery shots (ACP settings, guest form, member form) with private-message delivery and no captcha, plus three for its 10/18/2026 update gallery. The last of those sends a guest message through the form and shows the topic the contact bot posts. @@ -172,6 +173,14 @@ Saves PNG screenshots of an extension's or style's pages with [Playwright](https `phpbbmodders/posteripinviewtopic` gives the busiest topic's posts the IP address 8.8.8.8 (seeded posts come from 127.0.0.1, which the extension hides) and saves, as the admin, the address in a post's profile column and its lookup popup. The lookup is real: the board asks ip-api.com, so the run needs internet access. Use `--seed` with seed-forum's standard fixtures. +`phpbbmodders/restorejumpbox` needs `--seed-board` with a board built from phpbb/phpbb#6927, since the extension does nothing while core still has its own jumpbox, and seed-forum's standard fixtures. It saves, as a guest, the **Jump to** list open at the bottom of General Discussion (with the nested Member Projects, and without the Staff Room guests can't see), on a topic page and on a phone, and the extension's fieldset in ACP → General → Load settings. The board `db-matrix.sh` keeps is on port 8130: + +```bash +phpbb-test-board/db-matrix.sh -b master -d sqlite -P 6927 -k path/to/restorejumpbox +phpbb-test-board/screenshots.py path/to/restorejumpbox -o path/to/restorejumpbox/docs/images/screenshots \ + --seed-board ~/.cache/phpbb-release-tools/db-matrix/boards/master-pr6927-sqlite --port 8130 +``` + For example, ProMinoDeux's screenshots: ```bash diff --git a/phpbb-test-board/screenshots.py b/phpbb-test-board/screenshots.py index a33018a..29e84c5 100755 --- a/phpbb-test-board/screenshots.py +++ b/phpbb-test-board/screenshots.py @@ -10,6 +10,12 @@ SHOTS below, keyed by an extension's composer name or a style's name from style.cfg; add an entry for a new project. Restores the board afterwards. +With --seed-board the screenshots are taken on an installed seed-forum board +instead, with the extension already enabled on it: a phpBB 4.0 board, or one +built from an open phpbb/phpbb pull request (db-matrix.sh -P N -k builds and +keeps one). That board is not reset; only its name is changed for the +screenshots and put back afterwards. + The first run installs Playwright's Chromium into the user's cache. Exit status: 0 if every screenshot was saved, 1 if any failed, 2 for bad arguments. @@ -32,7 +38,7 @@ import requests # noqa: E402 from playwright.sync_api import Browser, Error as PlaywrightError, sync_playwright # noqa: E402 -from _board import Board, ext_name, is_style, parse_spec, required_packages, style_cfg # noqa: E402 +from _board import Board, SeedBoard, ext_name, is_style, parse_spec, required_packages, style_cfg # noqa: E402 BOARD_NAME = "Example board" BOARD_DESCRIPTION = "A phpBB board" @@ -1501,6 +1507,37 @@ def run_prune(p) -> None: shoot.shot("prunenotifications-acp-done", acp, admin=True, full_page=True, prepare=run_prune) +def shots_restorejumpbox(board: Board, shoot: Shooter, args: argparse.Namespace) -> None: + """phpbbmodders/restorejumpbox: the "Jump to" selector open at the bottom of a forum page, a topic + page and a forum page on a phone, all as a guest, and its ACP Load settings fieldset. + + The extension does nothing while core still has its own jumpbox, so it needs --seed-board with a + board built from phpbb/phpbb#6927 and seed-forum's bin/seed-standard-fixtures.php, for example the + one db-matrix.sh -b master -d sqlite -P 6927 -k keeps. General Discussion has the nested Member + Projects forum; the Staff Room is hidden from guests, so it is left out of their selector. + """ + if not isinstance(board, SeedBoard) or board.sql("SELECT config_value FROM phpbb_config " + "WHERE config_name = 'load_jumpbox'"): + raise RuntimeError("needs --seed-board with a phpBB built from phpbb/phpbb#6927 " + "(db-matrix.sh -b master -d sqlite -P 6927 -k)") + forum = board.sql("SELECT forum_id FROM phpbb_forums WHERE forum_name = 'General Discussion'")[0][0] + topic = board.sql("SELECT topic_id FROM phpbb_topics WHERE forum_id = ? " + "ORDER BY topic_posts_approved DESC, topic_id LIMIT 1", (int(forum),))[0][0] + + def open_jumpbox(p) -> None: + button = p.locator("#jumpbox .dropdown-trigger") + button.scroll_into_view_if_needed() + p.mouse.wheel(0, 10000) # to the bottom of the page, so the list has room to open upwards + button.click() + p.locator("#restorejumpbox-links").wait_for(state="visible") + + shoot.shot("forum", f"viewforum.php?f={forum}", prepare=open_jumpbox) + shoot.shot("topic", f"viewtopic.php?t={topic}", prepare=open_jumpbox) + shoot.shot("mobile", f"viewforum.php?f={forum}", phone=True, prepare=open_jumpbox) + shoot.shot("acp-load-settings", "adm/index.php?i=acp_board&mode=load&sid={sid}", admin=True, + selector="fieldset:has(input[name='config[restorejumpbox_visible]'])") + + SHOTS = { "phpbbmodders/translationhelper": shots_translationhelper, "phpbbmodders/prunenotifications": shots_prunenotifications, @@ -1517,6 +1554,7 @@ def run_prune(p) -> None: "phpbbmodders/newspage": shots_newspage, "phpbbmodders/posteripinviewtopic": shots_posteripinviewtopic, "phpbbmodders/printallposts": shots_printallposts, + "phpbbmodders/restorejumpbox": shots_restorejumpbox, "phpbbmodders/sfscompanion": shots_sfscompanion, "phpbbmodders/stopforumspam": shots_stopforumspam, "phpbbmodders/banhammer": shots_banhammer, @@ -1558,8 +1596,15 @@ def main() -> int: "(extensions only)") ap.add_argument("-b", "--board-dir", help="board directory created by setup-board.sh (default: $PHPBB_TEST_BOARD)") + ap.add_argument("-S", "--seed-board", metavar="BOARD_ROOT", + help="installed seed-forum board to use instead of --board-dir, with the extension already " + "enabled on it, for example a phpBB 4.0 or pull-request board kept by db-matrix.sh -k; " + "it is not reset (extensions only; --seed and --with don't apply)") + ap.add_argument("--seed-forum", metavar="DIR", default=str(Path(__file__).resolve().parents[2] / "seed-forum"), + help="seed-forum checkout used with --seed-board (default: seed-forum next to this repository)") ap.add_argument("-p", "--port", type=int, default=8083, - help="port for the temporary web server (default: 8083)") + help="port for the temporary web server (default: 8083); with --seed-board, the board's " + "own server_port") args = ap.parse_args() try: @@ -1574,12 +1619,20 @@ def main() -> int: style = is_style(repo, ref) if style and needed: ap.error("--with is for extensions; a style needs no other extensions") + if args.seed_board: + if style: + ap.error("--seed-board is for extensions; shoot a style on a setup-board.sh board") + if args.seed or needed: + ap.error("--seed and --with can't be combined with --seed-board; seed the board and enable " + "required extensions with seed-forum") name = style_cfg(repo, ref)["name"] if style else ext_name(repo, ref) if name not in SHOTS: ap.error(f"no screenshots defined for {name}; add them to SHOTS in {Path(__file__).name}") out_dir = Path(args.out).expanduser().resolve() out_dir.mkdir(parents=True, exist_ok=True) + if args.seed_board: + return shoot_seed_board(args, name, repo, out_dir) board = Board(args.board_dir, args.port) if style: ext, target = board.install_style(repo, ref) @@ -1604,23 +1657,7 @@ def main() -> int: board.sql("UPDATE phpbb_config SET config_value = ? WHERE config_name = 'sitename'", (BOARD_NAME,)) board.sql("UPDATE phpbb_config SET config_value = ? WHERE config_name = 'site_desc'", (BOARD_DESCRIPTION,)) board.purge_cache() - with board.serve(), sync_playwright() as playwright: - browser = ensure_chromium(playwright) - shooter = Shooter(board, browser, out_dir) - try: - SHOTS[ext](board, shooter, args) - finally: - browser.close() - for path in shooter.cleanup: - if Path(path).is_dir(): - shutil.rmtree(path, ignore_errors=True) - else: - Path(path).unlink(missing_ok=True) - errors = board.php_errors() - if errors: - print(f" [FAIL] PHP errors logged: {errors[0][:200]}") - print(f"== {len(shooter.saved)} saved, {len(shooter.failed)} failed") - return 1 if shooter.failed or errors else 0 + return take_shots(board, ext, args, out_dir) finally: if style: board.remove_style(target) @@ -1628,5 +1665,50 @@ def main() -> int: board.remove_ext(target) +def shoot_seed_board(args: argparse.Namespace, ext: str, repo: Path, out_dir: Path) -> int: + """Take an extension's screenshots on an installed seed-forum board, where it is already enabled. + + Nothing is installed or reset: only the board's name and description are + changed for the screenshots, then put back. + """ + board = SeedBoard(args.seed_board, args.port, args.seed_forum) + print(f"== {ext} ({repo.name}, as mounted on the board) on phpBB {board.version} -> {out_dir}") + active = board.sql("SELECT ext_active FROM phpbb_ext WHERE ext_name = ?", (ext,)) + if not active or str(active[0][0]) != "1": + print(f" [FAIL] {ext} is not enabled on {board.root}; enable it with seed-forum") + return 1 + names = dict(board.sql("SELECT config_name, config_value FROM phpbb_config " + "WHERE config_name IN ('sitename', 'site_desc')")) + try: + board.sql("UPDATE phpbb_config SET config_value = ? WHERE config_name = 'sitename'", (BOARD_NAME,)) + board.sql("UPDATE phpbb_config SET config_value = ? WHERE config_name = 'site_desc'", (BOARD_DESCRIPTION,)) + board.purge_cache() + return take_shots(board, ext, args, out_dir) + finally: + for config_name, value in names.items(): + board.sql("UPDATE phpbb_config SET config_value = ? WHERE config_name = ?", (value, config_name)) + board.purge_cache() + + +def take_shots(board: Board, ext: str, args: argparse.Namespace, out_dir: Path) -> int: + """Serve the board and run the project's SHOTS function; return the exit status.""" + with board.serve(), sync_playwright() as playwright: + browser = ensure_chromium(playwright) + shooter = Shooter(board, browser, out_dir) + try: + SHOTS[ext](board, shooter, args) + finally: + browser.close() + for path in shooter.cleanup: + if Path(path).is_dir(): + shutil.rmtree(path, ignore_errors=True) + else: + Path(path).unlink(missing_ok=True) + errors = board.php_errors() + if errors: + print(f" [FAIL] PHP errors logged: {errors[0][:200]}") + print(f"== {len(shooter.saved)} saved, {len(shooter.failed)} failed") + return 1 if shooter.failed or errors else 0 + if __name__ == "__main__": sys.exit(main()) diff --git a/tests/test-phpbb-test-board.sh b/tests/test-phpbb-test-board.sh index 21e373f..18eb341 100755 --- a/tests/test-phpbb-test-board.sh +++ b/tests/test-phpbb-test-board.sh @@ -128,6 +128,23 @@ check "feature_checks.py --seed-board without an installed board exits 1" \ check "feature_checks.py --seed-board with --with exits 2" \ exits_with 2 "$tools/feature_checks.py" "$tmp/repo" --seed-board "$tmp" --with "$tmp/repo" +# screenshots.py --seed-board: arguments and a missing board only; no board is built here +jumpbox="$tmp/jumpbox-ext" +git init -q "$jumpbox" +printf '{"name": "phpbbmodders/restorejumpbox", "type": "phpbb-extension"}\n' >"$jumpbox/composer.json" +git -C "$jumpbox" add composer.json +git -C "$jumpbox" -c user.name=test -c user.email=test@example.com commit -qm "test extension" +check "screenshots.py --help describes --seed-board" \ + output_has "--seed-board BOARD_ROOT" "$tools/screenshots.py" --help +check "screenshots.py --help lists Restore Jumpbox" \ + output_has "phpbbmodders/restorejumpbox" "$tools/screenshots.py" --help +check "screenshots.py --seed-board with --with exits 2" \ + exits_with 2 "$tools/screenshots.py" "$jumpbox" -o "$tmp/out" --seed-board "$tmp" --with "$renamed" +check "screenshots.py --seed-board with --seed exits 2" \ + exits_with 2 "$tools/screenshots.py" "$jumpbox" -o "$tmp/out" --seed-board "$tmp" --seed "$jumpbox/composer.json" +check "screenshots.py --seed-board without an installed board exits 1" \ + exits_with 1 "$tools/screenshots.py" "$jumpbox" -o "$tmp/out" --seed-board "$tmp/no-board" + # db-matrix.sh: option handling only; no board or container is started check "db-matrix.sh without arguments shows help and exits 0" exits_with 0 "$tools/db-matrix.sh" check "db-matrix.sh --help shows usage" output_has "Usage: db-matrix.sh" "$tools/db-matrix.sh" --help