analytics/test/plausible_web/live/installationv2_test.exs

714 lines
20 KiB
Elixir

defmodule PlausibleWeb.Live.InstallationV2Test do
use PlausibleWeb.ConnCase, async: true
use Plausible
use Plausible.Test.Support.DNS
import Phoenix.LiveViewTest
import Plausible.Test.Support.HTML
import Plausible.Teams.Test
alias Plausible.Site.TrackerScriptConfiguration
@migration_guide_link "https://plausible.io/docs/script-update-guide"
setup [:create_user, :log_in, :create_site]
setup %{site: site} do
FunWithFlags.enable(:scriptv2, for_actor: site)
:ok
end
describe "GET /:domain/installationv2" do
@tag :ee_only
test "renders loading installation screen on EE", %{conn: conn, site: site} do
resp = get(conn, "/#{site.domain}/installationv2") |> html_response(200)
assert resp =~ "animate-spin"
end
@tag :ce_build_only
test "no loading spinner, no GTM tab on CE", %{conn: conn, site: site} do
resp = get(conn, "/#{site.domain}/installationv2") |> html_response(200)
tabs_text = text_of_element(resp, "a[data-phx-link='patch']")
assert length(String.split(tabs_text)) == 3
assert tabs_text =~ "Script"
assert tabs_text =~ "WordPress"
assert tabs_text =~ "NPM"
refute resp =~ "animate-spin"
end
end
describe "LiveView" do
@tag :ee_only
test "detects installation type when mounted", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert text(html) =~ "Verify WordPress installation"
end
@tag :ee_only
test "When ?type=wordpress URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _} = get_lv(conn, site, "?type=wordpress")
html = render_async(lv, 500)
assert text(html) =~ "Verify WordPress installation"
end
@tag :ee_only
test "When ?type=gtm URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=gtm")
html = render_async(lv, 500)
assert text(html) =~ "Verify Tag Manager installation"
end
@tag :ee_only
test "When ?type=npm URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=npm")
html = render_async(lv, 500)
assert text(html) =~ "Verify NPM installation"
end
@tag :ee_only
test "When ?type=manual URL parameter is supplied, detected type is unused", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?type=manual")
html = render_async(lv, 500)
assert text(html) =~ "Verify Script installation"
end
@tag :ee_only
test "allows switching between installation tabs (EE)", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=manual")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
lv
|> element("a[href*=\"type=wordpress\"]")
|> render_click()
html = render(lv)
assert html =~ "Verify WordPress installation"
lv
|> element("a[href*=\"type=gtm\"]")
|> render_click()
html = render(lv)
assert html =~ "Verify Tag Manager installation"
lv
|> element("a[href*=\"type=npm\"]")
|> render_click()
html = render(lv)
assert html =~ "Verify NPM installation"
end
@tag :ce_build_only
test "allows switching between installation tabs (CE)", %{conn: conn, site: site} do
{lv, _html} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
lv
|> element("a[href*=\"type=wordpress\"]")
|> render_click()
html = render(lv)
assert html =~ "Verify WordPress installation"
end
test "manual installations has script snippet with expected ID", %{conn: conn, site: site} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
assert eventually(fn ->
html = render(lv)
{html =~ "Verify Script installation", html}
end)
html = render(lv)
config = Plausible.Repo.get_by!(TrackerScriptConfiguration, site_id: site.id)
assert html =~ "Privacy-friendly analytics by Plausible"
assert html =~ "/js/#{config.id}.js"
assert html =~ "async"
end
test "manual installation shows optional measurements", %{conn: conn, site: site} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
assert html =~ "Optional measurements"
assert html =~ "Outbound links"
assert html =~ "File downloads"
assert html =~ "Form submissions"
end
test "manual installation shows advanced options in disclosure", %{conn: conn, site: site} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
assert html =~ "Advanced options"
assert html =~ "Manual tagging"
assert html =~ "404 error pages"
assert html =~ "Hashed page paths"
assert html =~ "Custom properties"
assert html =~ "Ecommerce revenue"
end
test "toggling optional measurements updates tracker configuration", %{
conn: conn,
site: site
} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
config = TrackerScriptConfiguration |> Plausible.Repo.get_by!(site_id: site.id)
assert config.outbound_links == true
assert config.file_downloads == true
assert config.form_submissions == true
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => "manual",
"outbound_links" => "false",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
updated_config = TrackerScriptConfiguration |> Plausible.Repo.get_by!(site_id: site.id)
assert updated_config.outbound_links == false
assert updated_config.file_downloads == true
assert updated_config.form_submissions == true
end
on_ee do
for {type, expected_text} <- [
{"manual", "Verify Script installation"},
{"wordpress", "Verify WordPress installation"},
{"gtm", "Verify Tag Manager installation"},
{"npm", "Verify NPM installation"}
] do
test "submitting form with #{type} redirects to verification (EE)", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_manual()
{lv, _html} = get_lv(conn, site, "?type=#{unquote(type)}")
html = render_async(lv, 500)
assert html =~ unquote(expected_text)
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => unquote(type),
"outbound_links" => "true",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
assert_redirect(
lv,
Routes.site_path(conn, :verification, site.domain,
flow: "provisioning",
installation_type: unquote(type)
)
)
end
end
end
@tag :ce_build_only
test "submitting the form redirects to verification (CE)", %{conn: conn, site: site} do
{lv, _html} = get_lv(conn, site)
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => "manual",
"outbound_links" => "true",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
assert_redirect(
lv,
Routes.site_path(conn, :verification, site.domain,
flow: "provisioning",
installation_type: "manual"
)
)
end
test "404 goal gets created regardless of user options", %{conn: conn, site: site} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
# Test with all options disabled
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => "manual",
"outbound_links" => "false",
"file_downloads" => "false",
"form_submissions" => "false"
}
})
# 404 goal should still be created
goals = Plausible.Goals.for_site(site)
assert Enum.any?(goals, &(&1.event_name == "404"))
end
test "submitting form with review flow redirects to verification with flow param", %{
conn: conn,
site: site
} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _html} = get_lv(conn, site, "?type=manual&flow=review")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
lv
|> element("form[phx-submit='submit']")
|> render_submit(%{
"tracker_script_configuration" => %{
"installation_type" => "manual",
"outbound_links" => "true",
"file_downloads" => "true",
"form_submissions" => "true"
}
})
assert_redirect(
lv,
Routes.site_path(conn, :verification, site.domain,
flow: "review",
installation_type: "manual"
)
)
end
@tag :ee_only
test "detected WordPress installation shows special message", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert text(html) =~ "We've detected your website is using WordPress"
end
@tag :ee_only
test "if ratelimit for detection is exceeded, does not make detection request and falls back to recommending manual installation",
%{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
# exceed the rate limit for site detection
Plausible.RateLimit.check_rate(
Plausible.RateLimit,
"site_detection:#{site.domain}",
:timer.minutes(60),
1,
100
)
# this won't be used: if it were used, the output would be different
stub_detection_wordpress()
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
refute text(html) =~ "We've detected your website is using WordPress"
assert text(html) =~ "Verify Script installation"
end
@tag :ee_only
test "detected GTM installation shows special message", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_gtm()
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify Tag Manager installation"
assert text(html) =~ "We've detected your website is using Google Tag Manager"
end
@tag :ee_only
test "detected NPM installation shows npm tab", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => false,
"npm" => true,
"wordpressLikely" => false,
"wordpressPlugin" => false
})
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify NPM installation"
end
@tag :ee_only
test "shows v1 detection warning and migration guide link for manual installation", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_manual_with_v1()
{lv, _} = get_lv(conn, site, "?type=manual")
html = render_async(lv, 500)
assert text(html) =~ "Your website is running an outdated version of the tracking script"
assert element_exists?(html, "a[href='#{@migration_guide_link}']")
end
@tag :ce_build_only
test "shows v1 migration guide link for manual instructions", %{conn: conn, site: site} do
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert text(html) =~ "Still using the legacy snippet"
assert element_exists?(html, "a[href='#{@migration_guide_link}']")
end
test "does not render link to migrate guide on WordPress installation tab", %{
conn: conn,
site: site
} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_wordpress_with_v1()
end
{lv, _} = get_lv(conn, site, "?type=wordpress")
html = render_async(lv, 500)
assert html =~ "Verify WordPress installation"
refute element_exists?(html, "a[href='#{@migration_guide_link}']")
end
@tag :ee_only
test "falls back to manual installation when detection fails at dns check level", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain, [])
ExUnit.CaptureLog.capture_log(fn ->
{lv, _} = get_lv(conn, site)
assert eventually(fn ->
html = render(lv)
# Should default to manual installation when detection returns {:error, _}
{html =~ "Verify Script installation", html}
end)
end)
end
@tag :ee_only
test "falls back to manual installation when dns succeeds but detection fails", %{
conn: conn,
site: site
} do
stub_lookup_a_records(site.domain)
stub_detection_error()
ExUnit.CaptureLog.capture_log(fn ->
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
# Should default to manual installation when detection returns {:error, _}
assert html =~ "Verify Script installation"
end)
end
end
describe "Authorization" do
test "requires site access permissions", %{conn: conn} do
other_user = insert(:user)
other_site = new_site(owner: other_user)
assert_raise Ecto.NoResultsError, fn ->
get_lv(conn, other_site)
end
end
test "allows viewer access to installation page", %{conn: conn, user: user} do
site = new_site()
add_guest(site, user: user, role: :viewer)
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
end
test "allows editor access to installation page", %{conn: conn, user: user} do
site = new_site()
add_guest(site, user: user, role: :editor)
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
end
end
describe "URL Parameter Handling" do
test "falls back to manual installation when invalid installation type parameter supplied",
%{
conn: conn,
site: site
} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _} = get_lv(conn, site, "?type=invalid")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
end
test "falls back to provisioning flow when invalid flow parameter supplied", %{
conn: conn,
site: site
} do
on_ee do
stub_lookup_a_records(site.domain)
stub_detection_manual()
end
{lv, _} = get_lv(conn, site, "?flow=invalid")
html = render_async(lv, 500)
assert html =~ "Verify Script installation"
end
end
describe "Detection Result Combinations" do
@describetag :ee_only
test "When GTM + Wordpress detected, GTM takes precedence", %{conn: conn, site: site} do
stub_lookup_a_records(site.domain)
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => true,
"wordpressLikely" => true,
"wordpressPlugin" => false
})
{lv, _} = get_lv(conn, site)
html = render_async(lv, 500)
assert html =~ "Verify Tag Manager installation"
end
end
describe "Legacy Installations" do
@tag :ee_only
test "uses detected type in review flow when installation_type is nil", %{
conn: conn,
site: site
} do
_config =
PlausibleWeb.Tracker.get_or_create_tracker_script_configuration!(site, %{
installation_type: nil,
outbound_links: true,
file_downloads: false,
form_submissions: true
})
stub_lookup_a_records(site.domain)
stub_detection_wordpress()
{lv, _} = get_lv(conn, site, "?flow=review")
html = render_async(lv, 500)
assert html =~ "Verify WordPress installation"
end
end
defp stub_detection_manual do
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => false,
"npm" => false,
"wordpressLikely" => false,
"wordpressPlugin" => false
})
end
defp stub_detection_wordpress do
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => false,
"npm" => false,
"wordpressLikely" => true,
"wordpressPlugin" => false
})
end
defp stub_detection_gtm do
stub_detection_result(%{
"v1Detected" => false,
"gtmLikely" => true,
"npm" => false,
"wordpressLikely" => false,
"wordpressPlugin" => false
})
end
defp stub_detection_manual_with_v1 do
stub_detection_result(%{
"v1Detected" => true,
"gtmLikely" => false,
"npm" => false,
"wordpressLikely" => false,
"wordpressPlugin" => false
})
end
on_ee do
defp stub_detection_wordpress_with_v1 do
stub_detection_result(%{
"v1Detected" => true,
"gtmLikely" => false,
"npm" => false,
"wordpressLikely" => true,
"wordpressPlugin" => false
})
end
end
defp stub_detection_result(js_data) do
Req.Test.stub(:global, fn conn ->
conn
|> put_resp_content_type("application/json")
|> send_resp(200, Jason.encode!(%{"data" => Map.put(js_data, "completed", true)}))
end)
end
defp stub_detection_error do
Req.Test.stub(:global, fn conn ->
conn
|> put_resp_content_type("application/json")
|> send_resp(
200,
Jason.encode!(%{"data" => %{"error" => %{"message" => "Simulated browser error"}}})
)
end)
end
defp get_lv(conn, site, qs \\ nil) do
{:ok, lv, html} = live(conn, "/#{site.domain}/installationv2#{qs}")
{lv, html}
end
end