diff --git a/circle.yml b/circle.yml index 0904266a5d..6d8adda01e 100644 --- a/circle.yml +++ b/circle.yml @@ -461,12 +461,18 @@ commands: channel: <> version: $(node ./scripts/get-browser-version.js chrome:<>) - run: + name: Run driver tests in Cypress environment: CYPRESS_KONFIG_ENV: production command: | echo Current working directory is $PWD echo Total containers $CIRCLE_NODE_TOTAL + if [[ "<>" = "webkit" ]]; then + npx playwright install webkit + npx playwright install-deps webkit + fi + if [[ -v MAIN_RECORD_KEY ]]; then # internal PR if <>; then @@ -1565,6 +1571,13 @@ jobs: - run-driver-integration-tests: browser: electron + driver-integration-tests-webkit: + <<: *defaults + parallelism: 5 + steps: + - run-driver-integration-tests: + browser: webkit + driver-integration-tests-chrome-experimentalSessionAndOrigin: <<: *defaults resource_class: medium @@ -1603,6 +1616,15 @@ jobs: browser: electron experimentalSessionAndOrigin: true + driver-integration-tests-webkit-experimentalSessionAndOrigin: + <<: *defaults + resource_class: medium + parallelism: 5 + steps: + - run-driver-integration-tests: + browser: webkit + experimentalSessionAndOrigin: true + run-reporter-component-tests-chrome: <<: *defaults parameters: @@ -2359,6 +2381,11 @@ linux-x64-workflow: &linux-x64-workflow context: test-runner:cypress-record-key requires: - build + # TODO: Fix keyboard tests to fix the majority of these tests before re-enabling + # - driver-integration-tests-webkit: + # context: test-runner:cypress-record-key + # requires: + # - build - driver-integration-tests-chrome-experimentalSessionAndOrigin: context: test-runner:cypress-record-key requires: @@ -2375,6 +2402,11 @@ linux-x64-workflow: &linux-x64-workflow context: test-runner:cypress-record-key requires: - build + # TODO: Implement WebKit network automation to fix the majority of these tests before re-enabling + # - driver-integration-tests-webkit-experimentalSessionAndOrigin: + # context: test-runner:cypress-record-key + # requires: + # - build - run-frontend-shared-component-tests-chrome: context: [test-runner:cypress-record-key, test-runner:launchpad-tests, test-runner:percy] percy: true diff --git a/package.json b/package.json index 3ef2be0628..c33413a395 100644 --- a/package.json +++ b/package.json @@ -192,6 +192,7 @@ "mock-fs": "5.1.1", "p-defer": "^3.0.0", "patch-package": "6.4.7", + "playwright-webkit": "1.24.2", "pluralize": "8.0.0", "postinstall-postinstall": "2.0.0", "print-arch": "1.0.0", diff --git a/packages/app/src/runner/index.ts b/packages/app/src/runner/index.ts index 4e00d9955f..ab00b048c8 100644 --- a/packages/app/src/runner/index.ts +++ b/packages/app/src/runner/index.ts @@ -37,6 +37,12 @@ export function createWebsocket (socketIoRoute: string) { const ws = client(socketConfig) + ws.on('connect_error', () => { + // fall back to polling if websocket fails to connect (webkit) + // https://github.com/socketio/socket.io/discussions/3998#discussioncomment-972316 + ws.io.opts.transports = ['polling', 'websocket'] + }) + ws.on('connect', () => { ws.emit('runner:connected') }) diff --git a/packages/config/__snapshots__/validation.spec.ts.js b/packages/config/__snapshots__/validation.spec.ts.js index dfa2c276f1..a713874b36 100644 --- a/packages/config/__snapshots__/validation.spec.ts.js +++ b/packages/config/__snapshots__/validation.spec.ts.js @@ -177,7 +177,7 @@ exports['config/src/validation .isValidBrowser passes valid browsers and forms e "displayName": "Bad family browser", "family": "unknown family" }, - "type": "either chromium or firefox" + "type": "either chromium, firefox or webkit" } } ] diff --git a/packages/config/src/validation.ts b/packages/config/src/validation.ts index f872a8a1af..589f502e93 100644 --- a/packages/config/src/validation.ts +++ b/packages/config/src/validation.ts @@ -4,6 +4,7 @@ import * as _ from 'lodash' import * as is from 'check-more-types' import { commaListsOr } from 'common-tags' import Debug from 'debug' +import { BROWSER_FAMILY } from '@packages/types' const debug = Debug('cypress:server:validation') @@ -49,11 +50,8 @@ export const isValidBrowser = (browser: any): ErrResult | true => { return errMsg('name', browser, 'a non-empty string') } - // TODO: this is duplicated with browsers/index - const knownBrowserFamilies = ['chromium', 'firefox'] - - if (!is.oneOf(knownBrowserFamilies)(browser.family)) { - return errMsg('family', browser, commaListsOr`either ${knownBrowserFamilies}`) + if (!is.oneOf(BROWSER_FAMILY)(browser.family)) { + return errMsg('family', browser, commaListsOr`either ${BROWSER_FAMILY}`) } if (!is.unemptyString(browser.displayName)) { diff --git a/packages/frontend-shared/src/assets/browserLogos.ts b/packages/frontend-shared/src/assets/browserLogos.ts index bbc88bd470..5efed447f3 100644 --- a/packages/frontend-shared/src/assets/browserLogos.ts +++ b/packages/frontend-shared/src/assets/browserLogos.ts @@ -10,6 +10,7 @@ import edgeCanaryIcon from '../../../../node_modules/browser-logos/src/edge-cana import edgeDevIcon from '../../../../node_modules/browser-logos/src/edge-dev/edge-dev.png' import firefoxNightlyIcon from '../../../../node_modules/browser-logos/src/firefox-nightly/firefox-nightly.svg?url' import firefoxDeveloperEditionIcon from '../../../../node_modules/browser-logos/src/firefox-developer-edition/firefox-developer-edition.svg?url' +import webKitIcon from '../../../../node_modules/browser-logos/src/webkit/webkit.svg?url' import genericBrowserLogo from '@packages/frontend-shared/src/assets/logos/generic-browser.svg?url' export const allBrowsersIcons = { @@ -25,5 +26,6 @@ export const allBrowsersIcons = { 'Edge Canary': edgeCanaryIcon, 'Edge Beta': edgeBetaIcon, 'Edge Dev': edgeDevIcon, + 'WebKit': webKitIcon, 'generic': genericBrowserLogo, } diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index d404a9a603..fcbca18c4e 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -42,6 +42,7 @@ type Browser implements Node { enum BrowserFamily { chromium firefox + webkit } enum BrowserStatus { diff --git a/packages/server/lib/browsers/index.js b/packages/server/lib/browsers/index.js index 2df2cf5629..69df090f3c 100644 --- a/packages/server/lib/browsers/index.js +++ b/packages/server/lib/browsers/index.js @@ -6,9 +6,9 @@ const check = require('check-more-types') const { exec } = require('child_process') const util = require('util') const os = require('os') +const { BROWSER_FAMILY } = require('@packages/types') -// returns true if the passed string is a known browser family name -const isBrowserFamily = check.oneOf(['chromium', 'firefox']) +const isBrowserFamily = check.oneOf(BROWSER_FAMILY) let instance = null @@ -79,6 +79,10 @@ const getBrowserLauncher = function (browser) { if (browser.family === 'firefox') { return require('./firefox') } + + if (browser.family === 'webkit') { + return require('./webkit') + } } process.once('exit', () => kill(true, true)) diff --git a/packages/server/lib/browsers/utils.ts b/packages/server/lib/browsers/utils.ts index 41369fc14c..3963d8d7fb 100644 --- a/packages/server/lib/browsers/utils.ts +++ b/packages/server/lib/browsers/utils.ts @@ -5,11 +5,11 @@ import type { FoundBrowser } from '@packages/types' import * as errors from '../errors' import * as plugins from '../plugins' import { getError } from '@packages/errors' +import * as launcher from '@packages/launcher' const path = require('path') const debug = require('debug')('cypress:server:browsers:utils') const getPort = require('get-port') -const launcher = require('@packages/launcher') const { fs } = require('../util/fs') const extension = require('@packages/extension') const appData = require('../util/app_data') @@ -182,44 +182,93 @@ function extendLaunchOptionsFromPlugins (launchOptions, pluginConfigResult, opti return launchOptions } -const getBrowsers = () => { +const wkBrowserVersionRe = /BROWSER_VERSION = \'(?[^']+)\'/gm + +const getWebKitBrowserVersion = async () => { + try { + // this seems to be the only way to accurately capture the WebKit version - it's not exported, and invoking the webkit binary with `--version` does not give the correct result + // after launching the browser, this is available at browser.version(), but we don't have a browser instance til later + const pwCorePath = path.dirname(require.resolve('playwright-core', { paths: [process.cwd()] })) + const wkBrowserPath = path.join(pwCorePath, 'lib', 'server', 'webkit', 'wkBrowser.js') + const wkBrowserContents = await fs.readFile(wkBrowserPath) + const result = wkBrowserVersionRe.exec(wkBrowserContents) + + if (!result || !result.groups!.version) return '0' + + return result.groups!.version + } catch (err) { + debug('Error detecting WebKit browser version %o', err) + + return '0' + } +} + +const getWebKitBrowser = async () => { + try { + const modulePath = require.resolve('playwright-webkit', { paths: [process.cwd()] }) + const mod = require(modulePath) as typeof import('playwright-webkit') + const version = await getWebKitBrowserVersion() + + const browser: FoundBrowser = { + name: 'webkit', + channel: 'stable', + family: 'webkit', + displayName: 'WebKit', + version, + path: mod.webkit.executablePath(), + majorVersion: version.split('.')[0], + warning: 'WebKit support is not currently available in production.', + } + + return browser + } catch (err) { + debug('WebKit is enabled, but there was an error constructing the WebKit browser: %o', { err }) + + return + } +} + +const getBrowsers = async () => { debug('getBrowsers') - return launcher.detect() - .then((browsers: FoundBrowser[] = []) => { - let majorVersion + const browsers = await launcher.detect() + let majorVersion - debug('found browsers %o', { browsers }) + debug('found browsers %o', { browsers }) - if (!process.versions.electron) { - debug('not in electron, skipping adding electron browser') + if (!process.versions.electron) { + debug('not in electron, skipping adding electron browser') - return browsers - } + return browsers + } - // @ts-ignore - const version = process.versions.chrome || '' + // @ts-ignore + const version = process.versions.chrome || '' - if (version) { - majorVersion = getMajorVersion(version) - } + if (version) { + majorVersion = getMajorVersion(version) + } - const electronBrowser: FoundBrowser = { - name: 'electron', - channel: 'stable', - family: 'chromium', - displayName: 'Electron', - version, - path: '', - majorVersion, - info: 'Electron is the default browser that comes with Cypress. This is the default browser that runs in headless mode. Selecting this browser is useful when debugging. The version number indicates the underlying Chromium version that Electron uses.', - } + const electronBrowser: FoundBrowser = { + name: 'electron', + channel: 'stable', + family: 'chromium', + displayName: 'Electron', + version, + path: '', + majorVersion, + info: 'Electron is the default browser that comes with Cypress. This is the default browser that runs in headless mode. Selecting this browser is useful when debugging. The version number indicates the underlying Chromium version that Electron uses.', + } - // the internal version of Electron, which won't be detected by `launcher` - debug('adding Electron browser %o', electronBrowser) + browsers.push(electronBrowser) - return browsers.concat(electronBrowser) - }) + if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { + const wkBrowser = await getWebKitBrowser() + + if (wkBrowser) browsers.push(wkBrowser) + } + + return browsers } const isValidPathToBrowser = (str) => { @@ -247,47 +296,44 @@ const parseBrowserOption = (opt) => { function ensureAndGetByNameOrPath(nameOrPath: string, returnAll: false, browsers: FoundBrowser[]): Bluebird function ensureAndGetByNameOrPath(nameOrPath: string, returnAll: true, browsers: FoundBrowser[]): Bluebird -function ensureAndGetByNameOrPath (nameOrPath: string, returnAll = false, browsers: FoundBrowser[] = []) { - const findBrowsers = browsers.length ? Bluebird.resolve(browsers) : getBrowsers() +async function ensureAndGetByNameOrPath (nameOrPath: string, returnAll = false, prevKnownBrowsers: FoundBrowser[] = []) { + const browsers = prevKnownBrowsers.length ? prevKnownBrowsers : (await getBrowsers()) - return findBrowsers - .then((browsers: FoundBrowser[] = []) => { - const filter = parseBrowserOption(nameOrPath) + const filter = parseBrowserOption(nameOrPath) - debug('searching for browser %o', { nameOrPath, filter, knownBrowsers: browsers }) + debug('searching for browser %o', { nameOrPath, filter, knownBrowsers: browsers }) - // try to find the browser by name with the highest version property - const sortedBrowsers = _.sortBy(browsers, ['version']) + // try to find the browser by name with the highest version property + const sortedBrowsers = _.sortBy(browsers, ['version']) - const browser = _.findLast(sortedBrowsers, filter) + const browser = _.findLast(sortedBrowsers, filter) - if (browser) { - // short circuit if found + if (browser) { + // short circuit if found + if (returnAll) { + return browsers + } + + return browser + } + + // did the user give a bad name, or is this actually a path? + if (isValidPathToBrowser(nameOrPath)) { + // looks like a path - try to resolve it to a FoundBrowser + return launcher.detectByPath(nameOrPath) + .then((browser) => { if (returnAll) { - return browsers + return [browser].concat(browsers) } return browser - } + }).catch((err) => { + errors.throwErr('BROWSER_NOT_FOUND_BY_PATH', nameOrPath, err.message) + }) + } - // did the user give a bad name, or is this actually a path? - if (isValidPathToBrowser(nameOrPath)) { - // looks like a path - try to resolve it to a FoundBrowser - return launcher.detectByPath(nameOrPath) - .then((browser) => { - if (returnAll) { - return [browser].concat(browsers) - } - - return browser - }).catch((err) => { - errors.throwErr('BROWSER_NOT_FOUND_BY_PATH', nameOrPath, err.message) - }) - } - - // not a path, not found by name - throwBrowserNotFound(nameOrPath, browsers) - }) + // not a path, not found by name + throwBrowserNotFound(nameOrPath, browsers) } const formatBrowsersToOptions = (browsers) => { diff --git a/packages/server/lib/browsers/webkit-automation.ts b/packages/server/lib/browsers/webkit-automation.ts new file mode 100644 index 0000000000..6dc9e56e7f --- /dev/null +++ b/packages/server/lib/browsers/webkit-automation.ts @@ -0,0 +1,165 @@ +import _ from 'lodash' + +import type playwright from 'playwright-webkit' + +export type CyCookie = Pick & { + // use `undefined` instead of `unspecified` + sameSite?: 'no_restriction' | 'lax' | 'strict' +} + +type CookieFilter = { + name: string + domain: string +} + +const extensionMap = { + 'no_restriction': 'None', + 'lax': 'Lax', + 'strict': 'Strict', +} as const + +function convertSameSiteExtensionToCypress (str: CyCookie['sameSite']): 'None' | 'Lax' | 'Strict' | undefined { + return str ? extensionMap[str] : undefined +} + +const normalizeGetCookieProps = (cookie: any): CyCookie => { + if (cookie.expires === -1) { + delete cookie.expires + } + + // Use expirationDate instead of expires 🤷‍♀️ + cookie.expirationDate = cookie.expires + delete cookie.expires + + if (cookie.sameSite === 'None') { + cookie.sameSite = 'no_restriction' + } + + return cookie as CyCookie +} + +const normalizeSetCookieProps = (cookie: CyCookie): playwright.Cookie => { + return { + name: cookie.name, + value: cookie.value, + path: cookie.path, + domain: cookie.domain, + secure: cookie.secure, + httpOnly: cookie.httpOnly, + expires: cookie.expirationDate!, + sameSite: convertSameSiteExtensionToCypress(cookie.sameSite)!, + } +} + +const _domainIsWithinSuperdomain = (domain: string, suffix: string) => { + const suffixParts = suffix.split('.').filter(_.identity) + const domainParts = domain.split('.').filter(_.identity) + + return _.isEqual(suffixParts, domainParts.slice(domainParts.length - suffixParts.length)) +} + +const _cookieMatches = (cookie: any, filter: Record) => { + if (filter.domain && !(cookie.domain && _domainIsWithinSuperdomain(cookie.domain, filter.domain))) { + return false + } + + if (filter.path && filter.path !== cookie.path) { + return false + } + + if (filter.name && filter.name !== cookie.name) { + return false + } + + return true +} + +export class WebkitAutomation { + private context: playwright.BrowserContext + public reset: (url?: string) => Promise + + constructor (resetPage, private page: playwright.Page) { + this.context = page.context() + this.reset = async (url?: string) => { + this.page = await resetPage(url) + this.context = this.page.context() + } + } + + private async getCookies () { + const cookies = await this.context.cookies() + + return cookies.map(normalizeGetCookieProps) + } + + private async getCookie (filter: CookieFilter) { + const cookies = await this.context.cookies() + + if (!cookies.length) return null + + const cookie = cookies.find((cookie) => { + return _cookieMatches(cookie, { + domain: filter.domain, + name: filter.name, + }) + }) + + if (!cookie) return null + + return normalizeGetCookieProps(cookie) + } + + private async clearCookie (filter: CookieFilter) { + const allCookies = await this.context.cookies() + const persistCookies = allCookies.filter((cookie) => { + return !_cookieMatches(cookie, filter) + }) + + await this.context.clearCookies() + if (persistCookies.length) await this.context.addCookies(persistCookies) + } + + private async takeScreenshot (data) { + const buffer = await this.page.screenshot({ + fullPage: data.capture === 'fullPage', + timeout: 0, + type: 'png', + }) + + const b64data = buffer.toString('base64') + + return `data:image/png;base64,${b64data}` + } + + onRequest = async (message, data) => { + switch (message) { + case 'is:automation:client:connected': + return true + case 'get:cookies': + return await this.getCookies() + case 'get:cookie': + return await this.getCookie(data) + case 'set:cookie': + return await this.context.addCookies([normalizeSetCookieProps(data)]) + case 'add:cookies': + case 'set:cookies': + return await this.context.addCookies(data.map(normalizeSetCookieProps)) + case 'clear:cookies': + return await this.context.clearCookies() + case 'clear:cookie': + return await this.clearCookie(data) + case 'take:screenshot': + return await this.takeScreenshot(data) + case 'focus:browser:window': + return await this.context.pages[0]?.bringToFront() + case 'reset:browser:state': + return + case 'reset:browser:tabs:for:next:test': + if (data.shouldKeepTabOpen) return await this.reset() + + return await this.context.browser()?.close() + default: + throw new Error(`No automation handler registered for: '${message}'`) + } + } +} diff --git a/packages/server/lib/browsers/webkit.ts b/packages/server/lib/browsers/webkit.ts new file mode 100644 index 0000000000..fa7694ba1d --- /dev/null +++ b/packages/server/lib/browsers/webkit.ts @@ -0,0 +1,82 @@ +import Debug from 'debug' +import { EventEmitter } from 'events' +import type playwright from 'playwright-webkit' +import type { Browser, BrowserInstance } from './types' +import type { Automation } from '../automation' +import { WebkitAutomation } from './webkit-automation' + +const debug = Debug('cypress:server:browsers:webkit') + +let wkAutomation: WebkitAutomation | undefined + +export async function connectToNewSpec (browser: Browser, options, automation: Automation) { + if (!wkAutomation) throw new Error('connectToNewSpec called without wkAutomation') + + automation.use(wkAutomation) + await options.onInitializeNewBrowserTab() + await wkAutomation.reset(options.url) +} + +export async function open (browser: Browser, url, options: any = {}, automation: Automation): Promise { + // resolve pw from user's project path + const pwModulePath = require.resolve('playwright-webkit', { paths: [process.cwd()] }) + const pw = require(pwModulePath) as typeof playwright + + const pwBrowser = await pw.webkit.launch({ + proxy: { + server: options.proxyServer, + }, + downloadsPath: options.downloadsFolder, + headless: browser.isHeadless, + }) + + let pwPage: playwright.Page + + async function resetPage (_url) { + // new context comes with new cache + storage + const newContext = await pwBrowser.newContext({ + ignoreHTTPSErrors: true, + }) + const oldPwPage = pwPage + + pwPage = await newContext.newPage() + + let promises: Promise[] = [] + + if (oldPwPage) promises.push(oldPwPage.context().close()) + + if (_url) promises.push(pwPage.goto(_url)) + + if (promises.length) await Promise.all(promises) + + return pwPage + } + + pwPage = await resetPage(url) + + wkAutomation = new WebkitAutomation(resetPage, pwPage) + + automation.use(wkAutomation) + + class WkInstance extends EventEmitter implements BrowserInstance { + // TODO: how to obtain launched process PID from PW? this is used for process_profiler + pid = NaN + + constructor () { + super() + + pwBrowser.on('disconnected', () => { + debug('pwBrowser disconnected') + this.emit('exit') + }) + } + + async kill () { + debug('closing pwBrowser') + await pwBrowser.close() + wkAutomation = undefined + } + } + + return new WkInstance() +} diff --git a/packages/server/lib/controllers/runner.ts b/packages/server/lib/controllers/runner.ts index ffa8f1e417..64b759ea14 100644 --- a/packages/server/lib/controllers/runner.ts +++ b/packages/server/lib/controllers/runner.ts @@ -79,7 +79,8 @@ export const runner = { config.platform = os.platform() as PlatformName config.arch = os.arch() config.spec = spec ? { ...spec, name: spec.baseName } : null - config.browser = getCurrentBrowser() + // coerce type to allow string to be cast to BrowserFamily + config.browser = getCurrentBrowser() as Cypress.Browser config.exit = exit ?? true debug('serving runner index.html with config %o', diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index eae73438a7..2a870d702b 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1560,7 +1560,13 @@ module.exports = { return Promise.all([ system.info(), - browserUtils.ensureAndGetByNameOrPath(browserName, false, userBrowsers).tap(removeOldProfiles), + (async () => { + const browsers = await browserUtils.ensureAndGetByNameOrPath(browserName, false, userBrowsers) + + await removeOldProfiles(browsers) + + return browsers + })(), trashAssets(config), ]) .spread(async (sys = {}, browser = {}) => { diff --git a/packages/server/lib/server-base.ts b/packages/server/lib/server-base.ts index d2aba73ed2..5ae72a8357 100644 --- a/packages/server/lib/server-base.ts +++ b/packages/server/lib/server-base.ts @@ -123,6 +123,7 @@ export abstract class ServerBase { protected _graphqlWS?: WebSocketServer protected _eventBus: EventEmitter protected _remoteStates: RemoteStates + private getCurrentBrowser: undefined | (() => Browser) constructor () { this.isListening = false @@ -245,6 +246,8 @@ export abstract class ServerBase { exit, } + this.getCurrentBrowser = getCurrentBrowser + this.setupCrossOriginRequestHandling() this._remoteStates.addEventListeners(this.socket.localBus) @@ -437,6 +440,12 @@ export abstract class ServerBase { // bail if this is our own namespaced socket.io / graphql-ws request if (req.url.startsWith(socketIoRoute)) { + if (this.getCurrentBrowser && this.getCurrentBrowser()?.name === 'webkit') { + // webkit uses polling transport for websocket, which will not trigger socketAllowed.add(...) + // skip isRequestAllowed for webkit + return + } + if (!this.socketAllowed.isRequestAllowed(req)) { socket.write('HTTP/1.1 400 Bad Request\r\n\r\nRequest not made via a Cypress-launched browser.') socket.end() diff --git a/packages/server/lib/socket-base.ts b/packages/server/lib/socket-base.ts index 8decf012c6..b802379b09 100644 --- a/packages/server/lib/socket-base.ts +++ b/packages/server/lib/socket-base.ts @@ -139,7 +139,8 @@ export class SocketBase { }, destroyUpgrade: false, serveClient: false, - transports: ['websocket'], + // allow polling in dev-mode-only, remove once webkit is no longer gated behind development + transports: process.env.CYPRESS_INTERNAL_ENV === 'production' ? ['websocket'] : ['websocket', 'polling'], }) } diff --git a/packages/server/test/integration/websockets_spec.js b/packages/server/test/integration/websockets_spec.js index f9c19f8bd6..2aede4a53b 100644 --- a/packages/server/test/integration/websockets_spec.js +++ b/packages/server/test/integration/websockets_spec.js @@ -285,22 +285,24 @@ describe('Web Sockets', () => { }) }) - it('fails to connect via polling', function (done) { - this.wsClient = socketIo.client(wsUrl || this.cfg.proxyUrl, { - path: this.cfg.socketIoRoute, - transports: ['polling'], - rejectUnauthorized: false, - reconnection: false, - }) + // TODO: this test will currently fail because we allow polling in development mode + // for webkit support. Restore this test before WebKit is available in production. + // it('fails to connect via polling', function (done) { + // this.wsClient = socketIo.client(wsUrl || this.cfg.proxyUrl, { + // path: this.cfg.socketIoRoute, + // transports: ['polling'], + // rejectUnauthorized: false, + // reconnection: false, + // }) - this.wsClient.on('connect', () => { - return done(new Error('should not have been able to connect')) - }) + // this.wsClient.on('connect', () => { + // return done(new Error('should not have been able to connect')) + // }) - return this.wsClient.io.on('error', () => { - return done() - }) - }) + // return this.wsClient.io.on('error', () => { + // return done() + // }) + // }) }) } diff --git a/packages/types/src/browser.ts b/packages/types/src/browser.ts index 7b4c3f3210..82c68e4aab 100644 --- a/packages/types/src/browser.ts +++ b/packages/types/src/browser.ts @@ -1,6 +1,11 @@ -export const BROWSER_FAMILY = ['chromium', 'firefox'] as const +export const BROWSER_FAMILY = ['chromium', 'firefox', 'webkit'] -type BrowserName = 'electron' | 'chrome' | 'chromium' | 'firefox' | string +if (process.env.CYPRESS_INTERNAL_ENV === 'production') { + // gate webkit behind env, while still keeping types consistent + delete BROWSER_FAMILY[2] +} + +type BrowserName = 'electron' | 'chrome' | 'chromium' | 'firefox' | 'webkit' | string export type BrowserChannel = 'stable' | 'canary' | 'beta' | 'dev' | 'nightly' | string diff --git a/system-tests/__snapshots__/config_spec.js b/system-tests/__snapshots__/config_spec.js index 3b6d8b1393..1b3b34aed2 100644 --- a/system-tests/__snapshots__/config_spec.js +++ b/system-tests/__snapshots__/config_spec.js @@ -154,7 +154,7 @@ Your configFile at /foo/bar/.projects/config-with-invalid-browser/cypress.config The error occurred while validating the browsers list. -Expected family to be either chromium or firefox. +Expected family to be either chromium, firefox or webkit. Instead the value was: diff --git a/yarn.lock b/yarn.lock index dc382f2711..b048b3221d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11856,7 +11856,7 @@ chrome-har-capturer@0.13.4: chrome-remote-interface@0.31.1: version "0.31.1" - resolved "https://registry.yarnpkg.com/chrome-remote-interface/-/chrome-remote-interface-0.31.1.tgz#87c37c81f10d9f0832b6d6a42e52ac2c7ebb4008" + resolved "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.31.1.tgz#87c37c81f10d9f0832b6d6a42e52ac2c7ebb4008" integrity sha512-cvNTnXfx4kYCaeh2sEKrdlqZsYRleACPL47O8LrrjihVfBQbfPmf03vVqSSm7SIeqyo2P77ZXovrBAs4D/nopQ== dependencies: commander "2.11.x" @@ -26570,6 +26570,18 @@ platform@1.3.3: resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461" integrity sha1-ZGx3ARiZhwtqCQPnXpl+jlHadGE= +playwright-core@1.24.2: + version "1.24.2" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.24.2.tgz#47bc5adf3dcfcc297a5a7a332449c9009987db26" + integrity sha512-zfAoDoPY/0sDLsgSgLZwWmSCevIg1ym7CppBwllguVBNiHeixZkc1AdMuYUPZC6AdEYc4CxWEyLMBTw2YcmRrA== + +playwright-webkit@1.24.2: + version "1.24.2" + resolved "https://registry.npmjs.org/playwright-webkit/-/playwright-webkit-1.24.2.tgz#a81c59c75538172cadd2efcf2ec955dd9b2d9666" + integrity sha512-o0JJUWkQ228rNU+a14FVEPf7uUnA+cOrpKM4vsHqLew42sn4Q9ns0rl8pZwekv4u6054OMki4LbKDpvY5ulPgg== + dependencies: + playwright-core "1.24.2" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"