Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
"lint": "yarn concurrently 'yarn:eslint .' 'yarn:tsc --noEmit'",
Copy link
Contributor

@silesky silesky Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Can we remove all the old wdio dependencies?
  2. Can we update/commit the yarn.lock? (CI is failing)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Contributor Author

@neelkanth-kaushik neelkanth-kaushik Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is still failing after pushing yarn.lock. I will look into it separately once all the test cases have been migrated.

"eslint": "yarn run -T eslint",
"tsc": "tsc",
"concurrently": "yarn run -T concurrently --raw"
"concurrently": "yarn run -T concurrently --raw",
"playwright-test": "playwright test",
"playwright-test:headed": "playwright test --headed",
"playwright-test:debug": "playwright test --debug",
"serve": "http-server public -p 4567"
},
"installConfig": {
"hoistingLimits": "workspaces"
},
"devDependencies": {
"@internal/test-helpers": "workspace:^",
"@playwright/test": "^1.28.1",
"@segment/analytics-consent-tools": "workspace:^",
"@segment/analytics-consent-wrapper-onetrust": "workspace:^",
"@segment/analytics-next": "workspace:^",
Expand All @@ -29,6 +34,7 @@
"@wdio/types": "8",
"expect": "^29.4.1",
"globby": "^11.0.2",
"http-server": "14.1.1",
"wdio-intercept-service": "^4.4.0",
"webpack": "^5.94.0",
"webpack-cli": "^4.8.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { PlaywrightTestConfig } from '@playwright/test'

const PORT = 4567
const config: PlaywrightTestConfig = {
testDir: './src/tests',
timeout: 30000,
retries: 1,
webServer: {
command: 'yarn http-server public -p 4567',
port: 4567,
reuseExistingServer: !process.env.CI,
},
use: {
headless: true,
viewport: { width: 1280, height: 720 },
baseURL: `http://localhost:${PORT}`,
video: {
mode: 'off',
size: { width: 640, height: 480 },
},
},
}

export default config
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<h1>Hello World - Serving Analytics</h1>
<h2>Please Check Network tab</h2>
<p>This page can used as playground or run by webdriver.io</p>
<script src="/public/dist/consent-tools-vanilla-opt-in.bundle.js"></script>
<script src="dist/consent-tools-vanilla-opt-in.bundle.js"></script>
</body>

</html>
Original file line number Diff line number Diff line change
@@ -1,124 +1,116 @@
import { CDNSettingsBuilder } from '@internal/test-helpers'
import type { SegmentEvent } from '@segment/analytics-next'
import assert from 'assert'
import type { Matches } from 'webdriverio'

const waitUntilReady = () =>
browser.waitUntil(
() => browser.execute(() => document.readyState === 'complete'),
{
timeout: 10000,
}
)
import { Page, Route, Request } from '@playwright/test'

export abstract class BasePage {
constructor(protected page: string) {}
protected page: Page
protected pageFile: string

segmentTrackingApiReqs: any[] = []
fetchIntegrationReqs: any[] = []

segmentTrackingApiReqs: Matches[] = []
fetchIntegrationReqs: Matches[] = []
constructor(page: Page, pageFile: string) {
this.page = page
this.pageFile = pageFile
}

async load(): Promise<void> {
const baseURL = browser.options.baseUrl
assert(baseURL)
await this.mockAPIs()
await browser.url(baseURL + '/public/' + this.page)
await waitUntilReady()
await this.page.goto(`/${this.pageFile}`)
await this.page.waitForLoadState('load')
}

async clearStorage() {
await browser.deleteAllCookies()
await browser.execute(() => window.localStorage.clear())
await this.page.context().clearCookies()
await this.page.evaluate(() => {
window.localStorage.clear()
window.sessionStorage.clear()
})
}

getAllTrackingEvents(): SegmentEvent[] {
const reqBodies = this.segmentTrackingApiReqs.map((el) =>
JSON.parse(el.postData!)
)
return reqBodies
async cleanup() {
this.segmentTrackingApiReqs = []
this.fetchIntegrationReqs = []
await this.clearStorage()
}

getAllTrackingEvents(): any[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use SegmentEvent[] rather than any, like we were before? Ditto anywhere else 'any' is used

Copy link
Contributor Author

@neelkanth-kaushik neelkanth-kaushik Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated segmentTrackingApiReqs: SegmentEvent[] = [] but had to use fetchIntegrationReqs: CoreExtraContext[] = [] because of a linter error in the private async mockNextIntegrationsAPI() {...} at the line this.fetchIntegrationReqs.push({ url: request.url() }). The Error says: 'url' does not exist in type 'SegmentEvent'.

The only interface where url is available is [CoreExtraContext](https://github.com/segmentio/analytics-next/blob/master/packages/core/src/events/interfaces.ts). Being the only interface fitting in this scenario I assume it is fine to use it. What do you think?

return this.segmentTrackingApiReqs
}

getConsentChangedEvents(): SegmentEvent[] {
const reqBodies = this.getAllTrackingEvents()
const consentEvents = reqBodies.filter(
getConsentChangedEvents(): any[] {
return this.getAllTrackingEvents().filter(
(el) => el.event === 'Segment Consent Preference Updated'
)
return consentEvents
}

async cleanup() {
this.segmentTrackingApiReqs = []
this.fetchIntegrationReqs = []
await this.clearStorage()
get fetchIntegrationReqsData(): any[] {
return this.fetchIntegrationReqs
}

async mockAPIs() {
private async mockAPIs() {
await this.mockSegmentTrackingAPI()
await this.mockCDNSettingsAPI()
await this.mockNextIntegrationsAPI()
}

private async mockSegmentTrackingAPI(): Promise<void> {
const mock = await browser.mock('https://api.segment.io/v1/t', {
method: 'post',
})
mock.respond((mock) => {
this.segmentTrackingApiReqs.push(mock)
// response with status 200
return Promise.resolve({
statusCode: 200,
body: JSON.stringify({ success: true }),
})
})
private async mockSegmentTrackingAPI() {
await this.page.route(
'https://api.segment.io/v1/t',
async (route: Route, request: Request) => {
const postData = await request.postData()
const parsed = JSON.parse(postData || '{}')
this.segmentTrackingApiReqs.push(parsed) // store the parsed event object directly

await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true }),
})
}
)
}

private async mockNextIntegrationsAPI(): Promise<void> {
const mock = await browser.mock('**/next-integrations/**')
mock.respond((mock) => {
this.fetchIntegrationReqs.push(mock)
return Promise.resolve({
statusCode: 200,
body: 'console.log("mocking action and classic destinations")',
})
})
private async mockNextIntegrationsAPI() {
await this.page.route(
'**/next-integrations/**',
async (route: Route, request: Request) => {
this.fetchIntegrationReqs.push({ url: request.url() })
await route.fulfill({
status: 200,
body: 'console.log("mocking action and classic destinations")',
contentType: 'application/javascript',
})
}
)
}

/** * Mock the CDN Settings endpoint so that this can run offline
*/
private async mockCDNSettingsAPI(): Promise<void> {
const settings = new CDNSettingsBuilder({
writeKey: 'something',
})
.addActionDestinationSettings(
{
private async mockCDNSettingsAPI() {
Copy link
Contributor

@silesky silesky Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we still use CDNSettingsBuilder? The point is that it also updates remotePlugins correctly, which is important, and it’s an important helper to decouple our tests IMO

Copy link
Contributor Author

@neelkanth-kaushik neelkanth-kaushik Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-04-23 at 4 15 37 PM I have verified it again that the endpoint "https://cdn.segment.com/v1/projects/foo/settings" is returning 200 OK and there is no error or warning in the Playwright Inspector console as well.

Copy link
Contributor

@silesky silesky Apr 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not following -- great that it works, but is it a problem to use CDNSettingsBuilder instead like we were before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const settings = {
integrations: {
FullStory: {
url: 'https://cdn.segment.com/next-integrations/actions/fullstory-plugins/foo.js',
creationName: 'FullStory',
consentSettings: {
categories: ['FooCategory2'],
},
},
{
'Actions Amplitude': {
url: 'https://cdn.segment.com/next-integrations/actions/amplitude-plugins/foo.js',
creationName: 'Actions Amplitude',
consentSettings: {
categories: ['FooCategory1'],
},
}
)
.build()

const mock = await browser.mock('**/settings')
mock.respond(settings, {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
},
}

await this.page.route('**/settings', async (route: Route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(settings),
})
})
}

/**
* Hard reload the page
*/
reload() {
return browser.execute(() => window.location.reload())
async reload() {
await this.page.reload()
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
import { Page } from '@playwright/test'
import { BasePage } from './base-page'

export class ConsentToolsVanilla extends BasePage {
constructor(page: Page, file: string) {
super(page, file)
}

async clickGiveConsent() {
const button = await $('#give-consent')
return button.click()
await this.page.locator('#give-consent').click()
}

async clickDenyConsent() {
const button = await $('#give-consent')
return button.click()
await this.page.locator('#deny-consent').click()
}
}

export class ConsentToolsVanillaOptOut extends ConsentToolsVanilla {
constructor() {
super('consent-tools-vanilla-opt-out.html')
constructor(page: Page) {
super(page, 'consent-tools-vanilla-opt-out.html')
}
}

export class ConsentToolsVanillaOptIn extends ConsentToolsVanilla {
constructor() {
super('consent-tools-vanilla-opt-in.html')
constructor(page: Page) {
super(page, 'consent-tools-vanilla-opt-in.html')
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Page } from '@playwright/test'
import { BasePage } from './base-page'

class OneTrustPage extends BasePage {
constructor() {
super('onetrust.html')
export class OneTrustPage extends BasePage {
constructor(page: Page) {
super(page, 'onetrust.html')
}

get isOneTrustLoaded(): Promise<void> {
// @ts-ignore
return window.isOneTrustLoaded
// Check if OneTrust is loaded by evaluating a global variable on the page
async isOneTrustLoaded() {
// To Do
}

clickAcceptButtonAndClosePopup() {
return $('#onetrust-accept-btn-handler').click()
//Click the OneTrust accept button
async clickAcceptButtonAndClosePopup() {
// To Do
}
}

export default new OneTrustPage()
Loading
Loading