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]

Reply via email to