Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ global_job_config:
- 'if [ -f out/dependency-scan-trivy.json ]; then CHECK_TYPE=deps make check.generate-report; fi'
- 'if [ -f out/security-export.json ]; then artifact push workflow out/security-export.json -d scans/$SEMAPHORE_JOB_ID.json; fi'
- 'if [ -f out/security-summary.md ]; then artifact push job out/security-summary.md -d .semaphore/REPORT.md; fi'
- 'if [ -d out/screenshots ] && [ "$(ls -A out/screenshots)" ]; then artifact push workflow out/screenshots -d screenshots; fi'


after_pipeline:
task:
Expand Down
9 changes: 9 additions & 0 deletions front/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,18 @@
## Testing Guidelines
- Keep unit tests close to their modules and name `describe` blocks after the function under test.
- Wallaby browser specs need the Docker stack running; execute with `mix test test/browser`.
- Prefer the helpers in `Support.Browser`/`Support.Browser.Assertions` (`assert_stable/2`, `assert_flash_notice/2`, etc.) instead of calling `assert_text/2` or `assert_has/2` directly—these wrappers handle stale DOM retries and should be the default in new tests.
- When a flow depends on flashes or full-page redirects, assert against deterministic selectors (e.g., `#changes-notification p[data-test=...]`) rather than generic text nodes, and wait for the new page before chaining more actions.
- Seed every browser test with the features and permissions it needs (usually via `Support.Stubs.Feature.enable_feature/3` and `Support.Stubs.PermissionPatrol.allow_everything/2`) so the UI renders the buttons you intend to click.
- Keep destructive test fixtures scoped: create orgs/projects/users per test module using the stubs in `test/support/stubs`, and add explicit waits if a change propagates asynchronously (e.g., retention policies or self-hosted agents).
- Generate coverage with `mix coveralls.html` and `npm run coverage`.
- Update fixtures in `test/fixture` when workflow, API, or UI contracts change.

## Browser Testing Notes
- Use the `browser_test` macro from `FrontWeb.WallabyCase` for every Wallaby spec. It handles screenshot capture, session cleanup, and tags so failures are easier to debug.
- Chrome/Chromedriver now refuse to click invisible elements; always wait until the target button/link is visible (`assert_stable/2`, `assert_stable_text/2`) before clicking, and prefer selectors that match the final rendered node (not a parent wrapper).
- For modals, redirects, and flash messages, re-sync the page via `Support.Browser.assert_flash_notice/2` or another deterministic element to avoid stale references after navigation.

## Commit & Pull Request Guidelines
- Commits follow `type(scope): message (#issue)` (e.g., `fix(front): adjust mermaid rendering (#621)`).
- Each commit should bundle code, schema, and tests for a single concern.
Expand Down
2 changes: 1 addition & 1 deletion front/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
ARG ELIXIR_VERSION=1.14.5
ARG OTP_VERSION=25.3.2.21
ARG ALPINE_VERSION=3.19.7
ARG ALPINE_VERSION=3.22.2
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-alpine-${ALPINE_VERSION}"
ARG RUNNER_IMAGE="alpine:${ALPINE_VERSION}"

Expand Down
4 changes: 2 additions & 2 deletions front/config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ config :front,
docs_domain: "docs.semaphoretest.test",
start_reactor: true

config :wallaby, screenshot_dir: System.get_env("WALLABY_SCREENSHOTS") || "./out"
config :wallaby, screenshot_dir: System.get_env("WALLABY_SCREENSHOTS") || "./out/screenshots"
config :wallaby, screenshot_on_failure: true
config :wallaby, driver: Wallaby.Experimental.Chrome
config :wallaby, driver: Wallaby.Chrome
config :wallaby, max_wait_time: 10_000

config :joken, current_time_adapter: Support.TimeMock
Expand Down
2 changes: 1 addition & 1 deletion front/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ defmodule Front.Mixfile do
{:tentacat, github: "renderedtext/tentacat"},
{:httpoison, ">= 0.0.0"},
{:uuid, "~> 1.1"},
{:wallaby, "~> 0.23.0", runtime: false, only: [:dev, :test]},
{:wallaby, "~> 0.30", runtime: false, only: [:dev, :test]},
{:fun_registry, github: "renderedtext/fun-registry", only: [:dev, :test]},
{:grpc_mock, github: "renderedtext/grpc-mock", only: [:dev, :test]},
{:yaml_elixir, "~> 2.4"},
Expand Down
16 changes: 9 additions & 7 deletions front/mix.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions front/test/browser/account_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ defmodule Front.Browser.AccountTest do
end

describe "Okta welcome page" do
test "it welcomes the customer to semaphore", %{page_url: page_url, session: session} do
browser_test "it welcomes the customer to semaphore", %{page_url: page_url, session: session} do
page = visit(session, page_url)

assert_text(page, "Welcome")
end

test "it asks the customer to connect to a git provider account", %{
browser_test "it asks the customer to connect to a git provider account", %{
page_url: page_url,
session: session
} do
Expand Down
6 changes: 3 additions & 3 deletions front/test/browser/activity_monitor_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ defmodule Front.Browser.ActivityMonitorTest do
{:ok, %{page: page}}
end

test "active pipelines are displayed on the activity monitor page", %{page: page} do
browser_test "active pipelines are displayed on the activity monitor page", %{page: page} do
FrontWeb.Plugs.Development.ActivityMonitor.running_pipelines()
|> Enum.each(fn p ->
assert_text(page, p.commit_message)
end)
end

test "queuing pipelines are displayed on the activity monitor page", %{page: page} do
browser_test "queuing pipelines are displayed on the activity monitor page", %{page: page} do
assert_text(page, "Lobby")
assert_text(page, "(1)")
end

test "usage gauge for machines are presented on the activity monitor", %{page: page} do
browser_test "usage gauge for machines are presented on the activity monitor", %{page: page} do
assert_text(page, "e1-standard-2")
assert_text(page, "4/8")
end
Expand Down
18 changes: 11 additions & 7 deletions front/test/browser/audit_logs_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Front.Browser.AuditLogsTest do

import Wallaby.Query, only: [link: 1]

test "viewing audit logs that have project details", %{session: session} do
browser_test "viewing audit logs that have project details", %{session: session} do
user = Stubs.User.create_default()
org = Stubs.Organization.create_default()
Support.Stubs.Feature.enable_feature(org.id, :audit_logs)
Expand Down Expand Up @@ -83,7 +83,7 @@ defmodule Front.Browser.AuditLogsTest do
}}
end

test "project link is displayed", %{session: session, project: project} do
browser_test "project link is displayed", %{session: session, project: project} do
page = open(session)

assert_text(page, "Project:")
Expand All @@ -94,7 +94,7 @@ defmodule Front.Browser.AuditLogsTest do
assert Wallaby.Browser.current_path(page) == "/projects/#{project.name}"
end

test "branch link is displayed", %{session: session, branch: branch} do
browser_test "branch link is displayed", %{session: session, branch: branch} do
page = open(session)

assert_text(page, "Branch:")
Expand All @@ -105,7 +105,7 @@ defmodule Front.Browser.AuditLogsTest do
assert Wallaby.Browser.current_path(page) == "/branches/#{branch.id}"
end

test "workflow link is displayed", %{session: session, workflow: workflow, hook: hook} do
browser_test "workflow link is displayed", %{session: session, workflow: workflow, hook: hook} do
page = open(session)

assert_text(page, "Workflow:")
Expand All @@ -116,7 +116,11 @@ defmodule Front.Browser.AuditLogsTest do
assert Wallaby.Browser.current_path(page) == "/workflows/#{workflow.id}"
end

test "pipeline link is displayed", %{session: session, workflow: workflow, pipeline: pipeline} do
browser_test "pipeline link is displayed", %{
session: session,
workflow: workflow,
pipeline: pipeline
} do
page = open(session)

assert_text(page, "Pipeline:")
Expand All @@ -127,13 +131,13 @@ defmodule Front.Browser.AuditLogsTest do
assert Wallaby.Browser.current_path(page) == "/workflows/#{workflow.id}"
end

test "event description is visible", %{session: session} do
browser_test "event description is visible", %{session: session} do
page = open(session)

assert_text(page, "Triggered a promotion to Production")
end

test "agent name and IP address is visible", %{session: session} do
browser_test "agent name and IP address is visible", %{session: session} do
page = open(session)

assert_text(page, "s1-local-testing")
Expand Down
9 changes: 6 additions & 3 deletions front/test/browser/organization_settings/contacts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,24 @@ defmodule Front.Browser.OrganizationSettings.ContactsTest do
end

describe "form" do
test "when org does not have any contacts, it is empty", %{page: page} do
browser_test "when org does not have any contacts, it is empty", %{page: page} do
page
|> click(@contacts_tab)
|> assert_contact_forms_are_shown()
end

test "when org has only one contact set, display it's information", %{page: page, org: org} do
browser_test "when org has only one contact set, display it's information", %{
page: page,
org: org
} do
insert_contact(org.id, "CONTACT_TYPE_MAIN", "Joe")

page
|> click(@contacts_tab)
|> assert_contact_name_is_present("Joe")
end

test "fill out financial contact info", %{page: page} do
browser_test "fill out financial contact info", %{page: page} do
page
|> click(@contacts_tab)
|> submit_form()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule Front.Browser.OrganizationSettings.IpAllowListTest do
{:ok, %{page: page}}
end

test "IP allow list tab does not appear", %{page: page} do
browser_test "IP allow list tab does not appear", %{page: page} do
page |> refute_has(Query.text("IP Allow List"))
end
end
Expand All @@ -31,7 +31,7 @@ defmodule Front.Browser.OrganizationSettings.IpAllowListTest do
{:ok, %{page: page}}
end

test "IP allow list tab does not appear", %{page: page} do
browser_test "IP allow list tab does not appear", %{page: page} do
page |> refute_has(Query.text("IP Allow List"))
end
end
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
defmodule Front.Browser.OrganizationSettings.OktaIntegrationTest do
use FrontWeb.WallabyCase
alias Support.Stubs
alias Support.{Browser, Stubs}

@page_header Query.css("h1", text: "Okta Integration")
@setup_btn Query.css("a", text: "Set Up")
@okta_tab Query.css("a", text: "Okta Integration")
@okta_tab_active Query.css("a.bg-green", text: "Okta Integration")
@save_btn Query.button("Save")
@edit_link Query.link("Edit")

@saml_cert "---- BEGIN CERTIFICATE ---- \n .... \n ---- END CERTIFICATE ----"

Expand All @@ -16,19 +17,15 @@ defmodule Front.Browser.OrganizationSettings.OktaIntegrationTest do
Support.Stubs.Feature.enable_feature(org.id, :rbac__saml)
Support.Stubs.Feature.enable_feature(org.id, :permission_patrol)

Support.Stubs.PermissionPatrol.add_permissions(org.id, user.id, [
"organization.view",
"organization.okta.view",
"organization.okta.manage"
])
Support.Stubs.PermissionPatrol.allow_everything(org.id, user.id)

page = visit(session, "/settings")

{:ok, %{page: page}}
end

describe "visiting empty state" do
test "it describes what is okta and shows a button for setting it up", %{page: page} do
browser_test "it describes what is okta and shows a button for setting it up", %{page: page} do
page
|> click(@okta_tab)
|> assert_okta_tab_is_active()
Expand All @@ -37,60 +34,62 @@ defmodule Front.Browser.OrganizationSettings.OktaIntegrationTest do
end

describe "setting up a new okta integration" do
test "it asks for saml issues and certificate", %{page: page} do
browser_test "it asks for saml issues and certificate", %{page: page} do
page
|> navigate_to_setup_form()
|> assert_we_are_on_setup_form()
end

test "it displays errors if issuer or certificate are not filled in", %{page: page} do
browser_test "it displays errors if issuer or certificate are not filled in", %{page: page} do
page
|> navigate_to_setup_form()
|> submit_form()
|> assert_has(Query.text("SAML issuer can't be blank"))
|> assert_has(Query.text("Certificate can't be blank"))
end

test "it redirects back to empty state if you hit cancel", %{page: page} do
browser_test "it redirects back to empty state if you hit cancel", %{page: page} do
page
|> navigate_to_setup_form()
|> cancel_form()
|> assert_we_are_on_zero_state()
end

test "if the form is valid it completes the setup and shows the SCIM token", %{page: page} do
browser_test "if the form is valid it completes the setup and shows the SCIM token", %{
page: page
} do
page
|> set_up()
|> assert_has(Query.text("SCIM Authorization token"))
|> click_view_integration()
|> assert_has(Query.css("div.bg-green", text: "Connected"))
|> Browser.assert_stable(Query.css("div.bg-green", text: "Connected"))
end
end

describe "visiting an already set up integration" do
test "the page shows that okta is connected", %{page: page} do
browser_test "the page shows that okta is connected", %{page: page} do
page
|> set_up()
|> visit("/settings/okta")
|> assert_has(Query.css("div.bg-green", text: "Connected"))
|> Browser.assert_stable(Query.css("div.bg-green", text: "Connected"))
end
end

defp submit_form(page) do
page
|> Support.Browser.scroll_into_view("button[type=submit]")
|> assert_has(@save_btn)
|> click(@save_btn)
end

defp click_view_integration(page) do
page
|> Support.Browser.scroll_into_view("a.btn-primary")
|> assert_has(Query.link("View Integration"))
|> click(Query.link("View Integration"))
end

defp cancel_form(page) do
page
|> Support.Browser.scroll_into_view("button[type=submit]")
|> assert_has(Query.link("Cancel"))
|> click(Query.link("Cancel"))
end

Expand All @@ -109,9 +108,19 @@ defmodule Front.Browser.OrganizationSettings.OktaIntegrationTest do
end

defp navigate_to_setup_form(page) do
page
|> click(@okta_tab)
|> click(@setup_btn)
page = page |> click(@okta_tab)

cond do
has_element?(page, @setup_btn) ->
page |> click(@setup_btn)

has_element?(page, @edit_link) ->
page |> click(@edit_link)

true ->
# force the helpful assertion error
assert_we_are_on_zero_state(page)
end
end

defp assert_okta_tab_is_active(page) do
Expand All @@ -126,4 +135,8 @@ defmodule Front.Browser.OrganizationSettings.OktaIntegrationTest do
|> fill_in(Query.text_field("SAML Certificate"), with: @saml_cert)
|> submit_form()
end

defp has_element?(page, query) do
Browser.retry_on_stale(fn -> has?(page, query) end)
end
end
Loading