@@ -3,7 +3,7 @@ import CONFIG from "../constants/config"
33
44const BASE_URL = 'https://api.browserstack.com'
55
6- const getAuth = ( username ?:string , accessKey ?:string ) => {
6+ const getAuth = ( username ?: string , accessKey ?: string ) => {
77 return `Basic ${ Buffer . from ( `${ username || CONFIG . adminUsername } :${ accessKey || CONFIG . adminAccessKey } ` ) . toString ( 'base64' ) } `
88}
99
@@ -27,12 +27,177 @@ export const getAutomateSessionDetails: BrowserStackAPI['getAutomateSessionDetai
2727 return sessionDetailsJSON
2828}
2929
30- export const getParsedAutomateTextLogs = async ( session :AutomateSessionResponse ) => {
30+ export const getParsedAutomateTextLogs = async ( session : AutomateSessionResponse ) => {
3131 const logs = await download ( session . automation_session . logs ) ;
32- const result = parseAutomateTextLogs ( logs . split ( '\n' ) )
33- return result
34- }
32+ const lines = logs . split ( '\n' ) ;
33+
34+ const timestampRegex = / ^ \d { 4 } - \d { 2 } - \d { 2 } \d { 1 , 2 } : \d { 1 , 2 } : \d { 1 , 2 } : \d { 1 , 3 } / ;
35+
36+ const entries : string [ ] = [ ] ;
37+
38+ for ( const line of lines ) {
39+ if ( timestampRegex . test ( line ) ) {
40+ // New log entry → push as a new entry
41+ entries . push ( line ) ;
42+ } else if ( entries . length > 0 ) {
43+ // Continuation of previous entry → append
44+ entries [ entries . length - 1 ] += '\n' + line ;
45+ } else {
46+ // Edge case: first line doesn't start with timestamp
47+ entries . push ( line ) ;
48+ }
49+ }
50+
51+ console . log ( entries )
52+
53+ return parseAutomateTextLogs ( entries ) ;
54+ } ;
55+
56+ const sendRequest = async ( method : string , url : string , body : any = { } , auth : string ) => {
57+ delete body . fetchRawLogs ;
58+
59+ // BrowserStack WebDriver quirk: convert "text" → "value" array for sendKeys
60+ // if (util.getCommandName?.(url) === 'sendKeys' && !body['value'] && body['text']) {
61+ // body['value'] = body['text'].split('');
62+ // }
63+
64+ const headers = {
65+ 'Content-Type' : 'application/json; charset=utf-8' ,
66+ 'Accept' : 'application/json; charset=utf-8' ,
67+ 'Authorization' : auth ,
68+ } ;
69+
70+ const fetchOptions : RequestInit = {
71+ method,
72+ headers,
73+ body : method === 'POST' ? JSON . stringify ( body ) : undefined ,
74+ } ;
75+
76+ const response = await fetch ( url , fetchOptions ) ;
77+ const isJSON = response . headers . get ( 'content-type' ) ?. includes ( 'application/json' ) ;
78+ const data = isJSON ? await response . json ( ) : await response . text ( ) ;
79+
80+ if ( ! response . ok ) {
81+ throw new Error (
82+ `BrowserStack API Error: ${ response . status } ${ response . statusText } — ${ JSON . stringify ( data ) } `
83+ ) ;
84+ }
85+
86+ return data ;
87+ } ;
88+
89+ export const startBrowserStackSession : BrowserStackAPI [ 'startSession' ] = async (
90+ options : StartSessionOptions
91+ ) => {
92+ const auth = getAuth ( CONFIG . demoUsername , CONFIG . demoAccessKey ) ;
93+ const hubUrl =
94+ options . hubUrl ||
95+ CONFIG . hubUrl ;
96+
97+ const capabilities = options . capabilities ;
98+
99+ // WebDriver requires the payload to be under "capabilities" → "alwaysMatch"
100+ const body = {
101+ capabilities : {
102+ alwaysMatch : capabilities ,
103+ } ,
104+ } ;
105+ console . log ( body )
106+ const data = await sendRequest ( 'POST' , hubUrl + '/session' , body , auth ) ;
107+
108+ const sessionId =
109+ data ?. value ?. sessionId || data ?. sessionId || data ?. value ?. session_id ;
110+
111+ return {
112+ sessionId,
113+ raw : data ,
114+ } ;
115+ } ;
35116
36- export const startBrowserStackSession :BrowserStackAPI [ 'startSession' ] = async ( options :StartSessionOptions ) => {
117+ export const stopBrowserStackSession : BrowserStackAPI [ 'stopSession' ] = async (
118+ options : StopSessionOptions
119+ ) => {
120+ // Get auth credentials (can be per-user or from config defaults)
121+ const auth = getAuth ( CONFIG . demoUsername , CONFIG . demoAccessKey ) ;
122+
123+ // Determine hub URL (defaults to BrowserStack Selenium Hub)
124+ const hubUrl =
125+ options . hubUrl ||
126+ CONFIG . hubUrl ||
127+ 'https://hub-cloud.browserstack.com/wd/hub' ;
128+
129+ // Construct session endpoint
130+ const sessionUrl = `${ hubUrl } /session/${ options . sessionId } ` ;
131+
132+ // Perform DELETE request to end the session
133+ const response = await sendRequest ( 'DELETE' , sessionUrl , { } , auth ) ;
134+
135+ return {
136+ success : true ,
137+ sessionId : options . sessionId ,
138+ raw : response ,
139+ } ;
140+ } ;
141+
142+ export const executeCommand : BrowserStackAPI [ 'executeCommand' ] = async (
143+ options : ExecuteCommandOptions
144+ ) => {
145+ const { request, sessionId } = options ;
146+
147+ const hubUrl =
148+ options . hubUrl ||
149+ CONFIG . hubUrl ||
150+ 'https://hub-cloud.browserstack.com/wd/hub' ;
151+
152+ const auth = getAuth ( CONFIG . demoUsername , CONFIG . demoAccessKey ) ;
153+
154+ let endpoint = request . endpoint ;
155+ let body = request . data ;
156+
157+ return sendRequest (
158+ request . method ,
159+ `${ hubUrl } /session/${ sessionId } ${ endpoint } ` ,
160+ body ,
161+ auth
162+ ) ;
163+ } ;
164+
165+ /**
166+ * Deep-replaces all appearances of elementId inside objects and arrays.
167+ */
168+ function replaceElementIdDeep ( obj : any , newId : string ) : any {
169+ if ( obj === null || obj === undefined ) return obj ;
170+
171+ // Replace scalar strings equal to an elementId
172+ if ( typeof obj === "string" ) {
173+ return obj ;
174+ }
175+
176+ // Replace element reference objects
177+ if ( typeof obj === "object" ) {
178+ // Handle WebDriver element references
179+ if ( obj . ELEMENT ) obj . ELEMENT = newId ;
180+ if ( obj [ "element-6066-11e4-a52e-4f735466cecf" ] )
181+ obj [ "element-6066-11e4-a52e-4f735466cecf" ] = newId ;
182+
183+ // Handle W3C Actions API origin element
184+ if ( obj . type === "pointerMove" && obj . origin && typeof obj . origin === "object" ) {
185+ if ( obj . origin . ELEMENT || obj . origin [ "element-6066-11e4-a52e-4f735466cecf" ] ) {
186+ obj . origin = newId ;
187+ }
188+ }
189+
190+ // Deep recursion
191+ for ( const key of Object . keys ( obj ) ) {
192+ obj [ key ] = replaceElementIdDeep ( obj [ key ] , newId ) ;
193+ }
194+ }
195+
196+ // Handle array recursively
197+ if ( Array . isArray ( obj ) ) {
198+ return obj . map ( item => replaceElementIdDeep ( item , newId ) ) ;
199+ }
200+
201+ return obj ;
202+ }
37203
38- }
0 commit comments