| defmodule PlausibleWeb.Live.InstallationTest do |
| use PlausibleWeb.ConnCase |
| use Plausible.Test.Support.DNS |
|
|
| import Phoenix.LiveViewTest |
|
|
| alias Plausible.Site.TrackerScriptConfiguration |
|
|
| @migration_guide_link "https://plausible.io/docs/script-update-guide" |
|
|
| setup [:create_user, :log_in, :create_site] |
|
|
| describe "GET /:domain/installation" do |
| @tag :ee_only |
| test "renders loading installation screen on EE", %{conn: conn, site: site} do |
| resp = get(conn, "/#{site.domain}/installation") |> 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}/installation") |> 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" |
|
|
| |
| lv |
| |> element("form[phx-submit='submit']") |
| |> render_submit(%{ |
| "tracker_script_configuration" => %{ |
| "installation_type" => "manual", |
| "outbound_links" => "false", |
| "file_downloads" => "false", |
| "form_submissions" => "false" |
| } |
| }) |
|
|
| |
| 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) |
|
|
| |
| Plausible.RateLimit.check_rate( |
| Plausible.RateLimit, |
| "site_detection:#{site.domain}", |
| :timer.minutes(60), |
| 1, |
| 100 |
| ) |
|
|
| |
| 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) |
| |
| {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) |
| |
| 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(Plausible.InstallationSupport.Checks.Detection, 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(Plausible.InstallationSupport.Checks.Detection, 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}/installation#{qs}") |
|
|
| {lv, html} |
| end |
| end |
|
|