mirror of
https://github.com/plausible/analytics.git
synced 2024-11-22 18:52:38 +03:00
Use user-agent instead of screen_width to get device type (#2711)
* Use user-agent instead of screen_width to get device type Co-authored-by: eriknakata <erik.nakata5@gmail.com> * Fix credo * Log on unhandled UAInspector device type * Make 'browser' the default tab in devices report * Remove device tooltip * Remove screen_width from ingestion completely * Remove browserstack harness, run playwright directly * Select meta key based on OS platform * Run CI tests in parallel * Improve device match readability * Add changelog --------- Co-authored-by: eriknakata <erik.nakata5@gmail.com>
This commit is contained in:
parent
f26ca7da9f
commit
43bf7dd09f
31
.github/workflows/tracker.yml
vendored
31
.github/workflows/tracker.yml
vendored
@ -7,27 +7,18 @@ on:
|
||||
- 'tracker/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and test
|
||||
test:
|
||||
timeout-minutes: 15
|
||||
runs-on: buildjet-4vcpu-ubuntu-2004
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Read .tool-versions
|
||||
uses: marocchino/tool-versions-action@v1
|
||||
id: versions
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{steps.versions.outputs.nodejs}}
|
||||
- name: 'BrowserStack Env Setup'
|
||||
uses: 'browserstack/github-actions/setup-env@master'
|
||||
with:
|
||||
username: ${{ secrets.BROWSERSTACK_USERNAME }}
|
||||
access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
|
||||
build-name: 'BUILD_INFO'
|
||||
- run: npm install --prefix ./tracker
|
||||
- run: npm run deploy --prefix ./tracker
|
||||
- name: "Install Playwright dependencies"
|
||||
node-version: 16
|
||||
- name: Install dependencies
|
||||
run: npm --prefix ./tracker ci
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: ./tracker
|
||||
run: npx playwright install-deps
|
||||
- run: npm --prefix ./tracker test
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npm --prefix ./tracker test
|
||||
|
@ -24,6 +24,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Reject events with long URIs and data URIs plausible/analytics#2536
|
||||
- Always show direct traffic in sources reports plausible/analytics#2531
|
||||
- Stop recording XX and T1 country codes plausible/analytics#2556
|
||||
- Device type is now determined from the User-Agent instead of window.innerWidth plausible/analytics#2711
|
||||
|
||||
### Removed
|
||||
- Remove the ability to collapse the main graph plausible/analytics#2627
|
||||
|
@ -84,10 +84,6 @@ function ScreenSizes({ query, site }) {
|
||||
return iconFor(screenSize.name)
|
||||
}
|
||||
|
||||
function renderTooltipText(screenSize) {
|
||||
return EXPLANATION[screenSize.name]
|
||||
}
|
||||
|
||||
return (
|
||||
<ListReport
|
||||
fetchData={fetchData}
|
||||
@ -95,18 +91,10 @@ function ScreenSizes({ query, site }) {
|
||||
keyLabel="Screen size"
|
||||
query={query}
|
||||
renderIcon={renderIcon}
|
||||
tooltipText={renderTooltipText}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const EXPLANATION = {
|
||||
'Mobile': 'up to 576px',
|
||||
'Tablet': '576px to 992px',
|
||||
'Laptop': '992px to 1440px',
|
||||
'Desktop': 'above 1440px',
|
||||
}
|
||||
|
||||
function iconFor(screenSize) {
|
||||
if (screenSize === 'Mobile') {
|
||||
return (
|
||||
@ -135,7 +123,7 @@ export default class Devices extends React.Component {
|
||||
this.tabKey = `deviceTab__${props.site.domain}`
|
||||
const storedTab = storage.getItem(this.tabKey)
|
||||
this.state = {
|
||||
mode: storedTab || 'size'
|
||||
mode: storedTab || 'browser'
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,9 +188,9 @@ export default class Devices extends React.Component {
|
||||
<div className="flex justify-between w-full">
|
||||
<h3 className="font-bold dark:text-gray-100">Devices</h3>
|
||||
<div className="flex text-xs font-medium text-gray-500 dark:text-gray-400 space-x-2">
|
||||
{this.renderPill('Size', 'size')}
|
||||
{this.renderPill('Browser', 'browser')}
|
||||
{this.renderPill('OS', 'os')}
|
||||
{this.renderPill('Size', 'size')}
|
||||
</div>
|
||||
</div>
|
||||
{this.renderContent()}
|
||||
|
@ -89,7 +89,7 @@ export default function ListReport(props) {
|
||||
maxWidthDeduction={maxWidthDeduction}
|
||||
plot={valueKey}
|
||||
>
|
||||
<span className="flex px-2 py-1.5 group dark:text-gray-300 relative z-9 break-all" tooltip={props.tooltipText && props.tooltipText(listItem)}>
|
||||
<span className="flex px-2 py-1.5 group dark:text-gray-300 relative z-9 break-all">
|
||||
<Link onClick={props.onClick || noop} className="md:truncate block hover:underline" to={{search: query.toString()}}>
|
||||
{props.renderIcon && props.renderIcon(listItem)}
|
||||
{props.renderIcon && ' '}
|
||||
|
@ -90,7 +90,6 @@ defmodule Mix.Tasks.SendPageview do
|
||||
url: "http://#{domain}#{page}",
|
||||
domain: domain,
|
||||
referrer: referrer,
|
||||
width: 1666,
|
||||
props: props
|
||||
}
|
||||
end
|
||||
|
@ -95,7 +95,6 @@ defmodule Plausible.Ingestion.Event do
|
||||
&put_referrer/1,
|
||||
&put_utm_tags/1,
|
||||
&put_geolocation/1,
|
||||
&put_screen_size/1,
|
||||
&put_props/1,
|
||||
&put_salts/1,
|
||||
&put_user_id/1,
|
||||
@ -145,7 +144,8 @@ defmodule Plausible.Ingestion.Event do
|
||||
operating_system: os_name(user_agent),
|
||||
operating_system_version: os_version(user_agent),
|
||||
browser: browser_name(user_agent),
|
||||
browser_version: browser_version(user_agent)
|
||||
browser_version: browser_version(user_agent),
|
||||
screen_size: screen_size(user_agent)
|
||||
})
|
||||
|
||||
_any ->
|
||||
@ -190,19 +190,6 @@ defmodule Plausible.Ingestion.Event do
|
||||
update_attrs(event, result)
|
||||
end
|
||||
|
||||
defp put_screen_size(%__MODULE__{} = event) do
|
||||
screen_size =
|
||||
case event.request.screen_width do
|
||||
nil -> nil
|
||||
width when width < 576 -> "Mobile"
|
||||
width when width < 992 -> "Tablet"
|
||||
width when width < 1440 -> "Laptop"
|
||||
width when width >= 1440 -> "Desktop"
|
||||
end
|
||||
|
||||
update_attrs(event, %{screen_size: screen_size})
|
||||
end
|
||||
|
||||
defp put_props(%__MODULE__{request: %{props: %{} = props}} = event) do
|
||||
update_attrs(event, %{
|
||||
"meta.key": Map.keys(props),
|
||||
@ -324,6 +311,41 @@ defmodule Plausible.Ingestion.Event do
|
||||
end
|
||||
end
|
||||
|
||||
@mobile_types [
|
||||
"smartphone",
|
||||
"feature phone",
|
||||
"portable media player",
|
||||
"phablet",
|
||||
"wearable",
|
||||
"camera"
|
||||
]
|
||||
@tablet_types ["car browser", "tablet"]
|
||||
@desktop_types ["tv", "console", "desktop"]
|
||||
alias UAInspector.Result.Device
|
||||
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
|
||||
defp screen_size(ua) do
|
||||
case ua.device do
|
||||
%Device{type: t} when t in @mobile_types ->
|
||||
"Mobile"
|
||||
|
||||
%Device{type: t} when t in @tablet_types ->
|
||||
"Tablet"
|
||||
|
||||
%Device{type: t} when t in @desktop_types ->
|
||||
"Desktop"
|
||||
|
||||
%Device{type: type} ->
|
||||
Sentry.capture_message("Could not determine device type from UAInspector",
|
||||
extra: %{type: type}
|
||||
)
|
||||
|
||||
nil
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp browser_version(ua) do
|
||||
case ua.client do
|
||||
:unknown -> ""
|
||||
|
@ -16,7 +16,6 @@ defmodule Plausible.Ingestion.Request do
|
||||
field :hostname, :string
|
||||
field :referrer, :string
|
||||
field :domains, {:array, :string}
|
||||
field :screen_width, :string
|
||||
field :hash_mode, :string
|
||||
field :pathname, :string
|
||||
field :props, :map
|
||||
@ -89,7 +88,6 @@ defmodule Plausible.Ingestion.Request do
|
||||
changeset,
|
||||
event_name: request_body["n"] || request_body["name"],
|
||||
referrer: request_body["r"] || request_body["referrer"],
|
||||
screen_width: request_body["w"] || request_body["screen_width"],
|
||||
hash_mode: request_body["h"] || request_body["hashMode"],
|
||||
props: parse_props(request_body)
|
||||
)
|
||||
|
@ -142,7 +142,6 @@ defmodule Plausible.Ingestion.RequestTest do
|
||||
domain: "dummy.site",
|
||||
url: "http://dummy.site/index.html",
|
||||
referrer: "https://example.com",
|
||||
screen_width: 1024,
|
||||
hashMode: 1,
|
||||
props: %{
|
||||
"custom1" => "property1",
|
||||
@ -154,7 +153,6 @@ defmodule Plausible.Ingestion.RequestTest do
|
||||
|
||||
assert {:ok, request} = Request.build(conn)
|
||||
assert request.referrer == "https://example.com"
|
||||
assert request.screen_width == 1024
|
||||
assert request.hash_mode == 1
|
||||
assert request.props["custom1"] == "property1"
|
||||
assert request.props["custom2"] == "property2"
|
||||
|
@ -3,6 +3,8 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
use Plausible.ClickhouseRepo
|
||||
|
||||
@user_agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36"
|
||||
@user_agent_mobile "Mozilla/5.0 (Linux; Android 6.0; U007 Pro Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36"
|
||||
@user_agent_tablet "Mozilla/5.0 (Linux; U; Android 4.2.2; it-it; Surfing TAB B 9.7 3G Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30"
|
||||
|
||||
describe "POST /api/event" do
|
||||
setup do
|
||||
@ -15,8 +17,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
domain: domain,
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
referrer: "http://m.facebook.com/",
|
||||
screen_width: 1440
|
||||
referrer: "http://m.facebook.com/"
|
||||
}
|
||||
|
||||
conn =
|
||||
@ -36,8 +37,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
params = %{
|
||||
domain: domain,
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
screen_width: 1440
|
||||
url: "http://gigride.live/"
|
||||
}
|
||||
|
||||
conn =
|
||||
@ -72,8 +72,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
referrer: "http://m.facebook.com/",
|
||||
domain: "#{domain1},#{domain2}",
|
||||
screen_width: 1440
|
||||
domain: "#{domain1},#{domain2}"
|
||||
}
|
||||
|
||||
conn =
|
||||
@ -93,8 +92,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
domain: domain,
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
referrer: "http://m.facebook.com/",
|
||||
screen_width: 1440
|
||||
referrer: "http://m.facebook.com/"
|
||||
}
|
||||
|
||||
t1 = System.convert_time_unit(System.monotonic_time(), :native, :millisecond)
|
||||
@ -273,8 +271,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
domain: domain,
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
referrer: "https://www.1-best-seo.com",
|
||||
screen_width: 1440
|
||||
referrer: "https://www.1-best-seo.com"
|
||||
}
|
||||
|
||||
conn =
|
||||
@ -451,17 +448,16 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
assert pageview.referrer_source == ""
|
||||
end
|
||||
|
||||
test "screen size is calculated from screen_width", %{conn: conn, domain: domain} do
|
||||
test "screen size is calculated from user agent", %{conn: conn, domain: domain} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
screen_width: 480,
|
||||
domain: domain
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("user-agent", @user_agent)
|
||||
|> put_req_header("user-agent", @user_agent_mobile)
|
||||
|> post("/api/event", params)
|
||||
|
||||
pageview = get_event(domain)
|
||||
@ -470,7 +466,46 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
assert pageview.screen_size == "Mobile"
|
||||
end
|
||||
|
||||
test "screen size is nil if screen_width is missing", %{conn: conn, domain: domain} do
|
||||
test "screen size is nil if user agent is unknown", %{conn: conn, domain: domain} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
domain: domain
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("user-agent", "unknown UA")
|
||||
|> post("/api/event", params)
|
||||
|
||||
pageview = get_event(domain)
|
||||
|
||||
assert response(conn, 202) == "ok"
|
||||
assert pageview.screen_size == ""
|
||||
end
|
||||
|
||||
test "screen size is calculated from user_agent when is tablet", %{conn: conn, domain: domain} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
domain: domain
|
||||
}
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("user-agent", @user_agent_tablet)
|
||||
|> post("/api/event", params)
|
||||
|
||||
pageview = get_event(domain)
|
||||
|
||||
assert response(conn, 202) == "ok"
|
||||
assert pageview.screen_size == "Tablet"
|
||||
end
|
||||
|
||||
test "screen size is calculated from user_agent when is desktop", %{
|
||||
conn: conn,
|
||||
domain: domain
|
||||
} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://gigride.live/",
|
||||
@ -485,7 +520,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
pageview = get_event(domain)
|
||||
|
||||
assert response(conn, 202) == "ok"
|
||||
assert pageview.screen_size == ""
|
||||
assert pageview.screen_size == "Desktop"
|
||||
end
|
||||
|
||||
test "can trigger a custom event", %{conn: conn, domain: domain} do
|
||||
@ -875,8 +910,7 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
n: "pageview",
|
||||
u: "http://www.example.com/opportunity",
|
||||
d: domain,
|
||||
r: "https://facebook.com/page",
|
||||
w: 300
|
||||
r: "https://facebook.com/page"
|
||||
}
|
||||
|
||||
conn
|
||||
@ -887,7 +921,6 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
assert pageview.pathname == "/opportunity"
|
||||
assert pageview.referrer_source == "Facebook"
|
||||
assert pageview.referrer == "facebook.com/page"
|
||||
assert pageview.screen_size == "Mobile"
|
||||
end
|
||||
|
||||
test "records hash when in hash mode", %{conn: conn, domain: domain} do
|
||||
|
2039
tracker/package-lock.json
generated
2039
tracker/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,15 @@
|
||||
{
|
||||
"scripts": {
|
||||
"deploy": "node compile.js",
|
||||
"test": "npm run deploy && BROWSERSTACK_LOCAL=true npx playwright test --config=./test/support/playwright.config.js"
|
||||
"test": "npm run deploy && npx playwright test --config=./test/support/playwright.config.js",
|
||||
"start": "node test/support/server.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.22.2",
|
||||
"browserstack-local": "^1.4.8",
|
||||
"@playwright/test": "^1.31.0",
|
||||
"express": "^4.18.1",
|
||||
"generatorics": "^1.1.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"playwright": "^1.22.2",
|
||||
"uglify-js": "^3.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -70,7 +70,6 @@
|
||||
{{/if}}
|
||||
payload.d = scriptEl.getAttribute('data-domain')
|
||||
payload.r = document.referrer || null
|
||||
payload.w = window.innerWidth
|
||||
if (options && options.meta) {
|
||||
payload.m = JSON.stringify(options.meta)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
const { test } = require('./support/harness');
|
||||
const { mockRequest, mockManyRequests, expectCustomEvent } = require('./support/test-utils');
|
||||
const { expect } = require('@playwright/test');
|
||||
const { expect, test } = require('@playwright/test');
|
||||
const { LOCAL_SERVER_ADDR } = require('./support/server');
|
||||
|
||||
|
||||
|
@ -1,17 +1,16 @@
|
||||
const { test } = require('./support/harness');
|
||||
const { mockRequest, expectCustomEvent, isMac, mockManyRequests } = require('./support/test-utils');
|
||||
const { expect } = require('@playwright/test');
|
||||
const { mockRequest, expectCustomEvent, mockManyRequests, metaKey } = require('./support/test-utils');
|
||||
const { expect, test } = require('@playwright/test');
|
||||
const { LOCAL_SERVER_ADDR } = require('./support/server');
|
||||
|
||||
|
||||
test.describe('file-downloads extension', () => {
|
||||
test('sends event and does not start download when link opens in new tab', async ({ page }, workerInfo) => {
|
||||
test('sends event and does not start download when link opens in new tab', async ({ page }) => {
|
||||
await page.goto('/file-download.html')
|
||||
const downloadURL = await page.locator('#link').getAttribute('href')
|
||||
|
||||
const plausibleRequestMock = mockRequest(page, '/api/event')
|
||||
const downloadRequestMock = mockRequest(page, downloadURL)
|
||||
await page.click('#link', { modifiers: [isMac(workerInfo) ? 'Meta' : 'Control'] })
|
||||
await page.click('#link', { modifiers: [metaKey()] })
|
||||
|
||||
expectCustomEvent(await plausibleRequestMock, 'File Download', { url: downloadURL })
|
||||
expect(await downloadRequestMock, "should not make download request").toBeNull()
|
||||
|
@ -1,6 +1,5 @@
|
||||
const { test } = require('./support/harness');
|
||||
const { mockRequest } = require('./support/test-utils')
|
||||
const { expect } = require('@playwright/test');
|
||||
const { expect, test } = require('@playwright/test');
|
||||
|
||||
test.describe('combination of hash and exclusions script extensions', () => {
|
||||
test('excludes by hash part of the URL', async ({ page }) => {
|
||||
|
@ -1,17 +1,16 @@
|
||||
const { test } = require('./support/harness')
|
||||
const { mockRequest, isMac, expectCustomEvent } = require('./support/test-utils')
|
||||
const { expect } = require('@playwright/test');
|
||||
const { mockRequest, expectCustomEvent, metaKey } = require('./support/test-utils')
|
||||
const { expect, test } = require('@playwright/test');
|
||||
|
||||
test.describe('outbound-links extension', () => {
|
||||
|
||||
test('sends event and does not navigate when link opens in new tab', async ({ page }, workerInfo) => {
|
||||
test('sends event and does not navigate when link opens in new tab', async ({ page }) => {
|
||||
await page.goto('/outbound-link.html')
|
||||
const outboundURL = await page.locator('#link').getAttribute('href')
|
||||
|
||||
const plausibleRequestMock = mockRequest(page, '/api/event')
|
||||
const navigationRequestMock = mockRequest(page, outboundURL)
|
||||
|
||||
await page.click('#link', { modifiers: [isMac(workerInfo) ? 'Meta' : 'Control'] })
|
||||
await page.click('#link', { modifiers: [metaKey()] })
|
||||
|
||||
expectCustomEvent(await plausibleRequestMock, 'Outbound Link: Click', { url: outboundURL })
|
||||
expect(await navigationRequestMock, "should not have made navigation request").toBeNull()
|
||||
|
@ -1,6 +1,5 @@
|
||||
const { test } = require('./support/harness');
|
||||
const { mockRequest } = require('./support/test-utils')
|
||||
const { expect } = require('@playwright/test');
|
||||
const { expect, test } = require('@playwright/test');
|
||||
|
||||
test.describe('Basic installation', () => {
|
||||
test('Sends pageview automatically', async ({ page }) => {
|
||||
|
@ -1,16 +0,0 @@
|
||||
const BrowserStackLocal = require('browserstack-local');
|
||||
|
||||
exports.bsLocal = new BrowserStackLocal.Local();
|
||||
exports.BS_LOCAL_ARGS = {
|
||||
key: process.env.BROWSERSTACK_ACCESS_KEY
|
||||
};
|
||||
|
||||
exports.ensureCredentials = function() {
|
||||
if (!process.env.BROWSERSTACK_ACCESS_KEY) {
|
||||
throw 'Please configure process.env.BROWSERSTACK_ACCESS_KEY'
|
||||
}
|
||||
|
||||
if (!process.env.BROWSERSTACK_USERNAME) {
|
||||
throw 'Please configure process.env.BROWSERSTACK_USERNAME'
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// global-setup.js
|
||||
const { bsLocal, ensureCredentials, BS_LOCAL_ARGS } = require('./browserstack');
|
||||
const { promisify } = require('util');
|
||||
const { runLocalFileServer } = require('./server')
|
||||
|
||||
const sleep = promisify(setTimeout);
|
||||
const redColour = '\x1b[31m';
|
||||
const whiteColour = '\x1b[0m';
|
||||
|
||||
ensureCredentials()
|
||||
|
||||
module.exports = async () => {
|
||||
console.log('Starting BrowserStackLocal ...');
|
||||
runLocalFileServer()
|
||||
// Starts the Local instance with the required arguments
|
||||
let localResponseReceived = false;
|
||||
bsLocal.start(BS_LOCAL_ARGS, (err) => {
|
||||
if (err) {
|
||||
console.error(
|
||||
`${redColour}Error starting BrowserStackLocal${whiteColour}`
|
||||
);
|
||||
} else {
|
||||
console.log('BrowserStackLocal Started');
|
||||
}
|
||||
localResponseReceived = true;
|
||||
});
|
||||
while (!localResponseReceived) {
|
||||
await sleep(1000);
|
||||
}
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
// global-teardown.js
|
||||
const { bsLocal } = require('./browserstack');
|
||||
const { promisify } = require('util');
|
||||
const sleep = promisify(setTimeout);
|
||||
|
||||
module.exports = async () => {
|
||||
// Stop the Local instance after your test run is completed, i.e after driver.quit
|
||||
let localStopped = false;
|
||||
|
||||
if (bsLocal && bsLocal.isRunning()) {
|
||||
bsLocal.stop(() => {
|
||||
localStopped = true;
|
||||
console.log('Stopped BrowserStackLocal');
|
||||
});
|
||||
while (!localStopped) {
|
||||
await sleep(1000);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,92 +0,0 @@
|
||||
const base = require('@playwright/test');
|
||||
const cp = require('child_process');
|
||||
const clientPlaywrightVersion = cp
|
||||
.execSync('npx playwright --version')
|
||||
.toString()
|
||||
.trim()
|
||||
.split(' ')[1];
|
||||
|
||||
// BrowserStack Specific Capabilities.
|
||||
const caps = {
|
||||
browser: 'chrome',
|
||||
os: 'osx',
|
||||
os_version: 'catalina',
|
||||
name: 'My first playwright test',
|
||||
build: process.env.BROWSERSTACK_BUILD_NAME || `local-build-${new Date().getTime()}`,
|
||||
'browserstack.username': process.env.BROWSERSTACK_USERNAME || 'YOUR_USERNAME',
|
||||
'browserstack.accessKey':
|
||||
process.env.BROWSERSTACK_ACCESS_KEY || 'YOUR_ACCESS_KEY',
|
||||
'browserstack.local': process.env.BROWSERSTACK_LOCAL || false,
|
||||
'client.playwrightVersion': clientPlaywrightVersion,
|
||||
'browserstack.debug': true,
|
||||
'browserstack.networkLogs': true,
|
||||
'browserstack.networkLogsOptions': {
|
||||
"captureContent": "true"
|
||||
}
|
||||
};
|
||||
|
||||
// Patching the capabilities dynamically according to the project name.
|
||||
const patchCaps = (name, title) => {
|
||||
let combination = name.split(/@browserstack/)[0];
|
||||
let [browerCaps, osCaps] = combination.split(/:/);
|
||||
let [browser, browser_version] = browerCaps.split(/@/);
|
||||
let osCapsSplit = osCaps.split(/ /);
|
||||
let os = osCapsSplit.shift();
|
||||
let os_version = osCapsSplit.join(' ');
|
||||
|
||||
caps.browser = browser;
|
||||
if (browser.includes('playwright-')) {
|
||||
caps['browserstack.playwrightVersion'] = browser_version;
|
||||
} else {
|
||||
caps.browser_version = browser_version;
|
||||
}
|
||||
caps.os = os;
|
||||
caps.os_version = os_version;
|
||||
caps.name = title;
|
||||
};
|
||||
|
||||
const isHash = (entity) => Boolean(entity && typeof(entity) === "object" && !Array.isArray(entity));
|
||||
const nestedKeyValue = (hash, keys) => keys.reduce((hash, key) => (isHash(hash) ? hash[key] : undefined), hash);
|
||||
const isUndefined = val => (val === undefined || val === null || val === '');
|
||||
const evaluateSessionStatus = (status) => {
|
||||
if (!isUndefined(status)) {
|
||||
status = status.toLowerCase();
|
||||
}
|
||||
if (status === "passed") {
|
||||
return "passed";
|
||||
} else if (status === "failed" || status === "timedout") {
|
||||
return "failed";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
exports.test = base.test.extend({
|
||||
page: async ({ page, playwright }, use, testInfo) => {
|
||||
// Use BrowserStack Launched Browser according to capabilities for cross-browser testing.
|
||||
if (testInfo.project.name.match(/browserstack/)) {
|
||||
patchCaps(testInfo.project.name, `${testInfo.file} - ${testInfo.title}`);
|
||||
const vBrowser = await playwright.chromium.connect({
|
||||
wsEndpoint:
|
||||
`wss://cdp.browserstack.com/playwright?caps=` +
|
||||
`${encodeURIComponent(JSON.stringify(caps))}`,
|
||||
});
|
||||
const vContext = await vBrowser.newContext(testInfo.project.use);
|
||||
const vPage = await vContext.newPage();
|
||||
await use(vPage);
|
||||
const testResult = {
|
||||
action: 'setSessionStatus',
|
||||
arguments: {
|
||||
status: evaluateSessionStatus(testInfo.status),
|
||||
reason: nestedKeyValue(testInfo, ['error', 'message'])
|
||||
},
|
||||
};
|
||||
await vPage.evaluate(() => {},
|
||||
`browserstack_executor: ${JSON.stringify(testResult)}`);
|
||||
await vPage.close();
|
||||
await vBrowser.close();
|
||||
} else {
|
||||
use(page);
|
||||
}
|
||||
},
|
||||
});
|
@ -1,102 +1,37 @@
|
||||
// playwright.config.js
|
||||
// @ts-check
|
||||
const { LOCAL_SERVER_ADDR } = require('./server')
|
||||
const { devices } = require('@playwright/test');
|
||||
|
||||
/** @type {import('@playwright/test').PlaywrightTestConfig} */
|
||||
const config = {
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
module.exports = {
|
||||
testDir: '../',
|
||||
testMatch: '**/*.spec.js',
|
||||
// Use globalSetup & globalTearedown only if browserstack.local = true
|
||||
globalSetup: require.resolve('./global-setup'),
|
||||
globalTeardown: require.resolve('./global-teardown'),
|
||||
timeout: 60000,
|
||||
retries: 2,
|
||||
use: {
|
||||
viewport: null,
|
||||
baseURL: LOCAL_SERVER_ADDR
|
||||
},
|
||||
timeout: 60 * 1000,
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'list',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
projects: [
|
||||
// -- BrowserStack Projects --
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// name should be of the format 'browser@browser_version:os os_version@browserstack'
|
||||
{
|
||||
name: 'firefox',
|
||||
use: { ...devices['Desktop Firefox'] },
|
||||
},
|
||||
|
||||
// use playwright version (or '1.latest') instead of browser version for `playwright-browser`.
|
||||
|
||||
// see which playwright versions use which browser versions:
|
||||
// https://www.browserstack.com/docs/automate/playwright/playwright-browser-compatibility
|
||||
//
|
||||
// supported os and browser options:
|
||||
// https://www.browserstack.com/docs/automate/playwright/browsers-and-os
|
||||
|
||||
// Chrome on Mac
|
||||
{
|
||||
name: 'chrome@latest:OSX Monterey@browserstack',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'chrome@86:OSX Mojave@browserstack',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
},
|
||||
},
|
||||
// Chrome on Windows
|
||||
{
|
||||
name: 'chrome@latest:Windows 11@browserstack',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'chrome@86:Windows 10@browserstack',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
},
|
||||
},
|
||||
// Firefox on Mac
|
||||
{
|
||||
name: 'playwright-firefox@1.latest:OSX Big Sur@browserstack',
|
||||
use: {
|
||||
browserName: 'firefox',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'playwright-firefox@1.18.1:OSX Catalina@browserstack',
|
||||
use: {
|
||||
browserName: 'firefox',
|
||||
},
|
||||
},
|
||||
// Firefox on Windows
|
||||
{
|
||||
name: 'playwright-firefox@1.latest:Windows 11@browserstack',
|
||||
use: {
|
||||
browserName: 'firefox',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'playwright-firefox@1.18.1:Windows 10@browserstack',
|
||||
use: {
|
||||
browserName: 'firefox',
|
||||
},
|
||||
},
|
||||
// Safari on Mac
|
||||
{
|
||||
name: 'playwright-webkit@1.latest:OSX Monterey@browserstack',
|
||||
use: {
|
||||
browserName: 'webkit',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'playwright-webkit@1.19.1:OSX Big Sur@browserstack',
|
||||
use: {
|
||||
browserName: 'webkit',
|
||||
},
|
||||
name: 'webkit',
|
||||
use: { ...devices['Desktop Safari'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
module.exports = config;
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
port: 3000,
|
||||
},
|
||||
}
|
||||
|
@ -14,6 +14,14 @@ exports.mockRequest = function (page, path) {
|
||||
})
|
||||
}
|
||||
|
||||
exports.metaKey = function() {
|
||||
if (process.platform === 'darwin') {
|
||||
return 'Meta'
|
||||
} else {
|
||||
return 'Control'
|
||||
}
|
||||
}
|
||||
|
||||
// Mocks a specified number of HTTP requests with given path. Returns a promise that resolves to a
|
||||
// list of requests as soon as the specified number of requests is made, or 10 seconds has passed.
|
||||
exports.mockManyRequests = function(page, path, numberOfRequests) {
|
||||
@ -32,10 +40,6 @@ exports.mockManyRequests = function(page, path, numberOfRequests) {
|
||||
})
|
||||
}
|
||||
|
||||
exports.isMac = function (workerInfo) {
|
||||
return workerInfo.project.name.includes('OSX')
|
||||
}
|
||||
|
||||
exports.expectCustomEvent = function (request, eventName, eventProps) {
|
||||
const payload = request.postDataJSON()
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
const { test } = require('./support/harness')
|
||||
const { mockRequest, mockManyRequests, isMac, expectCustomEvent } = require('./support/test-utils')
|
||||
const { expect } = require('@playwright/test')
|
||||
const { mockRequest, mockManyRequests, expectCustomEvent, metaKey } = require('./support/test-utils')
|
||||
const { expect, test } = require('@playwright/test')
|
||||
|
||||
test.describe('tagged-events extension', () => {
|
||||
test('tracks a tagged link click with custom props + url prop', async ({ page }) => {
|
||||
@ -37,14 +36,14 @@ test.describe('tagged-events extension', () => {
|
||||
expectCustomEvent(requests[0], "Form Submit", {})
|
||||
});
|
||||
|
||||
test('tracks click and auxclick on any tagged HTML element', async ({ page }, workerInfo) => {
|
||||
test('tracks click and auxclick on any tagged HTML element', async ({ page }) => {
|
||||
await page.goto('/tagged-event.html')
|
||||
|
||||
const plausibleRequestMockList = mockManyRequests(page, '/api/event', 3)
|
||||
|
||||
await page.click('#button')
|
||||
await page.click('#span')
|
||||
await page.click('#div', { modifiers: [isMac(workerInfo) ? 'Meta' : 'Control'] })
|
||||
await page.click('#div', { modifiers: [metaKey()] })
|
||||
|
||||
const requests = await plausibleRequestMockList
|
||||
expect(requests.length).toBe(3)
|
||||
@ -68,12 +67,12 @@ test.describe('tagged-events extension', () => {
|
||||
expect((await navigationRequestMock).url()).toContain(linkURL)
|
||||
});
|
||||
|
||||
test('tracks tagged HTML elements when their child element is clicked', async ({ page }, workerInfo) => {
|
||||
test('tracks tagged HTML elements when their child element is clicked', async ({ page }) => {
|
||||
await page.goto('/tagged-event.html')
|
||||
|
||||
const plausibleRequestMockList = mockManyRequests(page, '/api/event', 2)
|
||||
|
||||
await page.click('#h2-with-link-parent', { modifiers: [isMac(workerInfo) ? 'Meta' : 'Control'] })
|
||||
await page.click('#h2-with-link-parent', { modifiers: [metaKey()] })
|
||||
await page.click('#link-with-div-parent')
|
||||
|
||||
const requests = await plausibleRequestMockList
|
||||
|
Loading…
Reference in New Issue
Block a user