From 52fdfc560f1098137d4cae760fa4d1721ce465ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Wed, 26 Nov 2025 17:00:56 +0100 Subject: [PATCH 1/3] feat: add support for Safari Technology Preview Usage: ``` jtr -b safari_tp ``` --- bin/browserList.js | 1 + lib/getBrowserString.js | 3 ++- selenium/createDriver.js | 30 ++++++++++++++++++++++++++---- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/bin/browserList.js b/bin/browserList.js index c15d708..7dcd79e 100644 --- a/bin/browserList.js +++ b/bin/browserList.js @@ -9,6 +9,7 @@ export const browsers = [ "firefox", "edge", "safari", + "safari_tp", "opera", "yandex", "IE Mobile", diff --git a/lib/getBrowserString.js b/lib/getBrowserString.js index 413a605..c4108c8 100644 --- a/lib/getBrowserString.js +++ b/lib/getBrowserString.js @@ -5,7 +5,8 @@ const browserMap = { ie: "IE", jsdom: "JSDOM", opera: "Opera", - safari: "Safari" + safari: "Safari", + safari_tp: "Safari Technology Preview" }; export function browserSupportsHeadless( browser ) { diff --git a/selenium/createDriver.js b/selenium/createDriver.js index f5eaa11..a05f575 100644 --- a/selenium/createDriver.js +++ b/selenium/createDriver.js @@ -2,6 +2,7 @@ import { Builder, Capabilities, logging } from "selenium-webdriver"; import Chrome from "selenium-webdriver/chrome.js"; import Edge from "selenium-webdriver/edge.js"; import Firefox from "selenium-webdriver/firefox.js"; +import Safari from "selenium-webdriver/safari.js"; import IE from "selenium-webdriver/ie.js"; import { browserSupportsHeadless } from "../lib/getBrowserString.js"; @@ -9,13 +10,19 @@ import { browserSupportsHeadless } from "../lib/getBrowserString.js"; const DRIVER_SCRIPT_TIMEOUT = 1000 * 60 * 10; export default async function createDriver( { browserName, headless, url, verbose } ) { - const capabilities = Capabilities[ browserName ](); + + // Support: Safari Technology Preview + // Handle safari_tp as safari with a custom binary path + const isSafariPreview = browserName === "safari_tp"; + const effectiveBrowserName = isSafariPreview ? "safari" : browserName; + + const capabilities = Capabilities[ effectiveBrowserName ](); // Support: IE 11+ // When those are set for IE, the process crashes with an error: // "Unable to match capability set 0: goog:loggingPrefs is an unknown // extension capability for IE". - if ( browserName !== "ie" ) { + if ( effectiveBrowserName !== "ie" ) { const prefs = new logging.Preferences(); prefs.setLevel( logging.Type.BROWSER, logging.Level.ALL ); capabilities.setLoggingPrefs( prefs ); @@ -55,6 +62,21 @@ export default async function createDriver( { browserName, headless, url, verbos edgeOptions.setEdgeChromiumBinaryPath( process.env.EDGE_BIN ); } + const safariOptions = new Safari.Options(); + + // Use Safari Technology Preview if safari_tp browser is selected + if ( isSafariPreview ) { + if ( verbose ) { + console.log( "Using Safari Technology Preview" ); + } + safariOptions.setTechnologyPreview( true ); + + // Without it, we're getting an error: + // SessionNotCreatedError: Could not create a session: Browser name does + // not match (requested: safari; available: Safari Technology Preview) + safariOptions.set( "browserName", "Safari Technology Preview" ); + } + const ieOptions = new IE.Options(); ieOptions.setEdgeChromium( true ); ieOptions.setEdgePath( "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" ); @@ -84,9 +106,9 @@ export default async function createDriver( { browserName, headless, url, verbos chromeOptions.addArguments( "--headless=new" ); firefoxOptions.addArguments( "--headless" ); edgeOptions.addArguments( "--headless=new" ); - if ( !browserSupportsHeadless( browserName ) ) { + if ( !browserSupportsHeadless( effectiveBrowserName ) ) { console.log( - `Headless mode is not supported for ${ browserName }.` + + `Headless mode is not supported for ${ effectiveBrowserName }.` + "Running in normal mode instead." ); } From 82fe1805ea4a0486ff985deca7a694aa97138c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Mon, 1 Dec 2025 18:54:12 +0100 Subject: [PATCH 2/3] fixup! feat: add support for Safari Technology Preview --- bin/browserList.js | 1 - bin/command.js | 5 +++++ browsers.js | 1 + lib/getBrowserString.js | 14 +++++++++++--- queue.js | 5 ++++- run.js | 9 ++++++++- selenium/createDriver.js | 25 +++++++++++++------------ 7 files changed, 42 insertions(+), 18 deletions(-) diff --git a/bin/browserList.js b/bin/browserList.js index 7dcd79e..c15d708 100644 --- a/bin/browserList.js +++ b/bin/browserList.js @@ -9,7 +9,6 @@ export const browsers = [ "firefox", "edge", "safari", - "safari_tp", "opera", "yandex", "IE Mobile", diff --git a/bin/command.js b/bin/command.js index de33e38..ac05d00 100755 --- a/bin/command.js +++ b/bin/command.js @@ -56,6 +56,11 @@ program "If using BrowserStack, specify browsers using --browserstack. " + "Choices: " + browsers.join( ", " ) + ". Defaults to Chrome." ) + .option( + "--safari-tp", + "Use Safari Technology Preview instead of regular Safari. " + + "Only works with --browser safari and cannot be used with --browserstack." + ) .option( "-m, --middleware ", "Add middleware to the test server by passing the path to a module that exports " + diff --git a/browsers.js b/browsers.js index e451b4e..63f6e01 100644 --- a/browsers.js +++ b/browsers.js @@ -90,6 +90,7 @@ export async function createBrowserWorker( url, browser, options, restarts = 0 ) const driver = await createDriver( { browserName: browser.browser, headless, + safariTp: options.safariTp, url, verbose } ); diff --git a/lib/getBrowserString.js b/lib/getBrowserString.js index c4108c8..263a811 100644 --- a/lib/getBrowserString.js +++ b/lib/getBrowserString.js @@ -5,8 +5,7 @@ const browserMap = { ie: "IE", jsdom: "JSDOM", opera: "Opera", - safari: "Safari", - safari_tp: "Safari Technology Preview" + safari: "Safari" }; export function browserSupportsHeadless( browser ) { @@ -26,10 +25,19 @@ export function getBrowserString( os, os_version: osVersion }, - headless + { + headless = false, + safariTp = false + } = {} ) { browser = browser.toLowerCase(); browser = browserMap[ browser ] || browser; + + // Handle Safari Technology Preview + if ( safariTp && browser === "Safari" ) { + browser = "Safari Technology Preview"; + } + let str = browser; if ( browserVersion ) { str += ` ${ browserVersion }`; diff --git a/queue.js b/queue.js index 3f1fc2a..ccf8741 100644 --- a/queue.js +++ b/queue.js @@ -77,7 +77,10 @@ export async function hardRetryTest( reportId, maxHardRetries ) { export function addRun( url, browser, options ) { queue.push( { browser, - fullBrowser: getBrowserString( browser ), + fullBrowser: getBrowserString( browser, { + headless: options.headless, + safariTp: options.safariTp + } ), hardRetries: 0, id: options.reportId, url, diff --git a/run.js b/run.js index 4886402..195195a 100644 --- a/run.js +++ b/run.js @@ -34,6 +34,7 @@ export async function run( { retries = 0, run: runs = [], runId, + safariTp, testUrl: testUrls = [], verbose } ) { @@ -45,6 +46,11 @@ export async function run( { "Cannot run in headless mode and debug mode at the same time." ); } + if ( safariTp && browserstack ) { + throw new Error( + "Cannot use --safari-tp with --browserstack." + ); + } // Ensure baseUrl ends with a slash if ( !rendsWithSlash.test( baseUrl ) ) { @@ -255,7 +261,7 @@ export async function run( { } function queueRun( browser, { run, testUrl } = {} ) { - const fullBrowser = getBrowserString( browser, headless ); + const fullBrowser = getBrowserString( browser, { headless, safariTp } ); const reportId = generateHash( `${ hashValue }-${ run }-${ testUrl }-${ fullBrowser }` ); @@ -287,6 +293,7 @@ export async function run( { headless, run, reportId, + safariTp, testUrl, tunnelId, verbose diff --git a/selenium/createDriver.js b/selenium/createDriver.js index a05f575..7840df0 100644 --- a/selenium/createDriver.js +++ b/selenium/createDriver.js @@ -9,20 +9,20 @@ import { browserSupportsHeadless } from "../lib/getBrowserString.js"; // Set script timeout to 10min const DRIVER_SCRIPT_TIMEOUT = 1000 * 60 * 10; -export default async function createDriver( { browserName, headless, url, verbose } ) { +export default async function createDriver( { + browserName, url, + headless = false, + verbose = false, + safariTp = false +} ) { - // Support: Safari Technology Preview - // Handle safari_tp as safari with a custom binary path - const isSafariPreview = browserName === "safari_tp"; - const effectiveBrowserName = isSafariPreview ? "safari" : browserName; - - const capabilities = Capabilities[ effectiveBrowserName ](); + const capabilities = Capabilities[ browserName ](); // Support: IE 11+ // When those are set for IE, the process crashes with an error: // "Unable to match capability set 0: goog:loggingPrefs is an unknown // extension capability for IE". - if ( effectiveBrowserName !== "ie" ) { + if ( browserName !== "ie" ) { const prefs = new logging.Preferences(); prefs.setLevel( logging.Type.BROWSER, logging.Level.ALL ); capabilities.setLoggingPrefs( prefs ); @@ -64,8 +64,8 @@ export default async function createDriver( { browserName, headless, url, verbos const safariOptions = new Safari.Options(); - // Use Safari Technology Preview if safari_tp browser is selected - if ( isSafariPreview ) { + // Use Safari Technology Preview if --safari-tp flag is provided + if ( safariTp ) { if ( verbose ) { console.log( "Using Safari Technology Preview" ); } @@ -106,9 +106,9 @@ export default async function createDriver( { browserName, headless, url, verbos chromeOptions.addArguments( "--headless=new" ); firefoxOptions.addArguments( "--headless" ); edgeOptions.addArguments( "--headless=new" ); - if ( !browserSupportsHeadless( effectiveBrowserName ) ) { + if ( !browserSupportsHeadless( browserName ) ) { console.log( - `Headless mode is not supported for ${ effectiveBrowserName }.` + + `Headless mode is not supported for ${ browserName }.` + "Running in normal mode instead." ); } @@ -118,6 +118,7 @@ export default async function createDriver( { browserName, headless, url, verbos .setChromeOptions( chromeOptions ) .setFirefoxOptions( firefoxOptions ) .setEdgeOptions( edgeOptions ) + .setSafariOptions( safariOptions ) .setIeOptions( ieOptions ); if ( ieService ) { From f6659b40057065a8e0573be7a1f771ad872c625b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82e=CC=A8biowski-Owczarek?= Date: Tue, 2 Dec 2025 00:18:41 +0100 Subject: [PATCH 3/3] fixup! feat: add support for Safari Technology Preview --- browsers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/browsers.js b/browsers.js index 63f6e01..1be7b83 100644 --- a/browsers.js +++ b/browsers.js @@ -49,7 +49,10 @@ export async function createBrowserWorker( url, browser, options, restarts = 0 ) ) }` ); } - const { browserstack, debug, headless, reportId, runId, tunnelId, verbose } = options; + const { + browserstack, debug, headless, reportId, runId, safariTp, tunnelId, verbose + } = options; + while ( await maxWorkersReached( options ) ) { if ( verbose ) { console.log( "\nWaiting for available sessions..." ); @@ -90,7 +93,7 @@ export async function createBrowserWorker( url, browser, options, restarts = 0 ) const driver = await createDriver( { browserName: browser.browser, headless, - safariTp: options.safariTp, + safariTp, url, verbose } );