This is an automated email from the ASF dual-hosted git repository. sbp pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git
commit fe4ce6e75e80a581ec520c575c3c6de8d18ce21e Author: Sean B. Palmer <[email protected]> AuthorDate: Wed Dec 10 20:42:56 2025 +0000 Add tests for the JS used on the announce page --- tests/e2e/announce/__init__.py | 16 ++++++ tests/e2e/announce/conftest.py | 111 +++++++++++++++++++++++++++++++++++++++++ tests/e2e/announce/helpers.py | 25 ++++++++++ tests/e2e/announce/test_get.py | 105 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 257 insertions(+) diff --git a/tests/e2e/announce/__init__.py b/tests/e2e/announce/__init__.py new file mode 100644 index 0000000..13a8339 --- /dev/null +++ b/tests/e2e/announce/__init__.py @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. diff --git a/tests/e2e/announce/conftest.py b/tests/e2e/announce/conftest.py new file mode 100644 index 0000000..85e55eb --- /dev/null +++ b/tests/e2e/announce/conftest.py @@ -0,0 +1,111 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from __future__ import annotations + +import pathlib +import time +from typing import TYPE_CHECKING, Final + +import e2e.helpers as helpers # type: ignore[reportMissingImports] +import pytest + +if TYPE_CHECKING: + from collections.abc import Generator + + from playwright.sync_api import Browser, BrowserContext, Page + +PROJECT_NAME: Final[str] = "test" +# TODO: We need a convention to scope this per test +VERSION_NAME: Final[str] = "0.1+announce" +FILE_NAME: Final[str] = "apache-test-0.2.tar.gz" +CURRENT_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.resolve() +ANNOUNCE_URL: Final[str] = f"/announce/{PROJECT_NAME}/{VERSION_NAME}" + + [email protected](scope="module") +def announce_context(browser: Browser) -> Generator[BrowserContext]: + """Create a release in the finish phase.""" + + context = browser.new_context( + ignore_https_errors=True, + # Needed for the copy variable buttons + permissions=["clipboard-read", "clipboard-write"], + ) + page = context.new_page() + + helpers.log_in(page) + + helpers.visit(page, f"/start/{PROJECT_NAME}") + page.locator("input#version_name").fill(VERSION_NAME) + page.get_by_role("button", name="Start new release").click() + page.wait_for_url(f"**/compose/{PROJECT_NAME}/{VERSION_NAME}") + + helpers.visit(page, f"/upload/{PROJECT_NAME}/{VERSION_NAME}") + page.locator('input[name="file_data"]').set_input_files(f"{CURRENT_DIR}/../test_files/{FILE_NAME}") + page.get_by_role("button", name="Add files").click() + page.wait_for_url(f"**/compose/{PROJECT_NAME}/{VERSION_NAME}") + + helpers.visit(page, f"/compose/{PROJECT_NAME}/{VERSION_NAME}") + _wait_for_tasks_banner_hidden(page, timeout=60000) + + page.locator('a[title="Start a vote on this draft"]').click() + page.wait_for_load_state() + + page.get_by_role("button", name="Send vote email").click() + page.wait_for_url(f"**/vote/{PROJECT_NAME}/{VERSION_NAME}") + + helpers.visit(page, f"/vote/{PROJECT_NAME}/{VERSION_NAME}") + _poll_for_vote_thread_link(page) + + resolve_form = page.locator(f'form[action="/resolve/{PROJECT_NAME}/{VERSION_NAME}"]') + resolve_form.get_by_role("button", name="Resolve vote").click() + page.wait_for_url(f"**/resolve/{PROJECT_NAME}/{VERSION_NAME}") + + page.locator('input[name="vote_result"][value="Passed"]').check() + page.get_by_role("button", name="Resolve vote").click() + page.wait_for_url(f"**/finish/{PROJECT_NAME}/{VERSION_NAME}") + + page.close() + + yield context + + context.close() + + [email protected] +def page_announce(announce_context: BrowserContext) -> Generator[Page]: + """Navigate to the announce page with a fresh page for each test.""" + page = announce_context.new_page() + helpers.visit(page, ANNOUNCE_URL) + yield page + page.close() + + +def _poll_for_vote_thread_link(page: Page, max_attempts: int = 30) -> None: + """Poll for the vote task to be completed.""" + thread_link_locator = page.locator('a:has-text("view thread")') + for _ in range(max_attempts): + if thread_link_locator.is_visible(timeout=500): + return + time.sleep(0.5) + page.reload() + + +def _wait_for_tasks_banner_hidden(page: Page, timeout: int = 30000) -> None: + """Wait for all background tasks to be completed.""" + page.wait_for_selector("#ongoing-tasks-banner", state="hidden", timeout=timeout) diff --git a/tests/e2e/announce/helpers.py b/tests/e2e/announce/helpers.py new file mode 100644 index 0000000..af77ca3 --- /dev/null +++ b/tests/e2e/announce/helpers.py @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +from playwright.sync_api import Locator, Page + + +def fill_path_suffix(page: Page, value: str) -> Locator: + """Fill the download path suffix input and return the help text locator.""" + help_text = page.locator("#download_path_suffix + .form-text") + page.locator("#download_path_suffix").fill(value) + return help_text diff --git a/tests/e2e/announce/test_get.py b/tests/e2e/announce/test_get.py new file mode 100644 index 0000000..6e8aa64 --- /dev/null +++ b/tests/e2e/announce/test_get.py @@ -0,0 +1,105 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import e2e.announce.helpers as helpers # type: ignore[reportMissingImports] +from playwright.sync_api import Page, expect + + +def test_copy_variable_button_shows_feedback(page_announce: Page) -> None: + """Clicking a copy variable button should show feedback.""" + variables_tab = page_announce.get_by_role("tab", name="Variables") + variables_tab.click() + + copy_button = page_announce.locator(".copy-var-btn").first + expect(copy_button).to_have_text("Copy") + + copy_button.click() + + expect(copy_button).to_have_text("Copied!") + + +def test_path_adds_leading_slash(page_announce: Page) -> None: + """Paths without a leading '/' should have one added.""" + help_text = helpers.fill_path_suffix(page_announce, "apple/banana") + expect(help_text).to_contain_text("/apple/banana/") + + +def test_path_adds_trailing_slash(page_announce: Page) -> None: + """Paths without a trailing '/' should have one added.""" + help_text = helpers.fill_path_suffix(page_announce, "/apple/banana") + expect(help_text).to_contain_text("/apple/banana/") + + +def test_path_normalises_dot_slash_prefix(page_announce: Page) -> None: + """Paths starting with './' should have it converted to '/'.""" + help_text = helpers.fill_path_suffix(page_announce, "./apple") + expect(help_text).to_contain_text("/apple/") + expect(help_text).not_to_contain_text("./") + + +def test_path_normalises_single_dot(page_announce: Page) -> None: + """A path of '.' should be normalised to '/'.""" + import re + + help_text = helpers.fill_path_suffix(page_announce, ".") + expect(help_text).to_have_text(re.compile(r"/$")) + + +def test_path_rejects_double_dots(page_announce: Page) -> None: + """Paths containing '..' should show an error message.""" + help_text = helpers.fill_path_suffix(page_announce, "../etc/passwd") + expect(help_text).to_contain_text("must not contain .. or //") + + +def test_path_rejects_double_slashes(page_announce: Page) -> None: + """Paths containing '//' should show an error message.""" + help_text = helpers.fill_path_suffix(page_announce, "apple//banana") + expect(help_text).to_contain_text("must not contain .. or //") + + +def test_path_rejects_hidden_directory(page_announce: Page) -> None: + """Paths containing '/.' should show an error message.""" + help_text = helpers.fill_path_suffix(page_announce, "/apple/.hidden/banana") + expect(help_text).to_contain_text("must not contain /.") + + +def test_preview_loads_on_page_load(page_announce: Page) -> None: + """The preview should automatically fetch and display on page load.""" + preview_tab = page_announce.get_by_role("tab", name="Preview") + preview_tab.click() + + preview_content = page_announce.locator("#announce-body-preview-content") + expect(preview_content).to_be_visible() + expect(preview_content).not_to_be_empty() + + +def test_preview_updates_on_body_input(page_announce: Page) -> None: + """Typing in the body textarea should update the preview.""" + preview_tab = page_announce.get_by_role("tab", name="Preview") + preview_content = page_announce.locator("#announce-body-preview-content") + + preview_tab.click() + initial_preview = preview_content.text_content() + + edit_tab = page_announce.get_by_role("tab", name="Edit") + edit_tab.click() + + body_textarea = page_announce.locator("#body") + body_textarea.fill("Custom test announcement body content") + + preview_tab.click() + expect(preview_content).not_to_have_text(initial_preview or "") --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
