1
+ from datetime import datetime , timedelta
1
2
from os import environ
3
+ from time import sleep
2
4
from typing import List , Union
3
5
4
6
from gql import Client , gql
5
7
from gql .transport .requests import RequestsHTTPTransport
6
8
from requests .auth import HTTPBasicAuth
7
9
8
10
from saucelabs_visual .regions import Region
9
- from saucelabs_visual .typing import IgnoreRegion , FullPageConfig
11
+ from saucelabs_visual .typing import IgnoreRegion , FullPageConfig , DiffingMethod , BuildStatus
10
12
11
13
12
14
class SauceLabsVisual :
13
15
client : Client = None
14
16
build_id : Union [str , None ] = None
17
+ build_url : Union [str , None ] = None
15
18
meta_cache : dict = {}
19
+ region : Region = None
16
20
17
21
def __init__ (self ):
18
22
self ._create_client ()
@@ -27,7 +31,8 @@ def _create_client(self):
27
31
'`SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` environment variables.'
28
32
)
29
33
30
- region_url = Region .from_name (environ .get ("SAUCE_REGION" ) or 'us-west-1' ).graphql_endpoint
34
+ self .region = Region .from_name (environ .get ("SAUCE_REGION" ) or 'us-west-1' )
35
+ region_url = self .region .graphql_endpoint
31
36
transport = RequestsHTTPTransport (url = region_url , auth = HTTPBasicAuth (username , access_key ))
32
37
self .client = Client (transport = transport , execute_timeout = 90 )
33
38
@@ -36,12 +41,12 @@ def get_client(self) -> Client:
36
41
37
42
def create_build (
38
43
self ,
39
- name : str = environ .get ('SAUCE_VISUAL_BUILD_NAME' ) or None ,
40
- project : str = environ .get ('SAUCE_VISUAL_PROJECT' ) or None ,
41
- branch : str = environ .get ('SAUCE_VISUAL_BRANCH' ) or None ,
42
- default_branch : str = environ .get ('SAUCE_VISUAL_DEFAULT_BRANCH' ) or None ,
43
- custom_id : str = environ .get ('SAUCE_VISUAL_CUSTOM_ID' ) or None ,
44
- keep_alive_timeout : int = None
44
+ name : Union [ str , None ] = environ .get ('SAUCE_VISUAL_BUILD_NAME' ),
45
+ project : Union [ str , None ] = environ .get ('SAUCE_VISUAL_PROJECT' ),
46
+ branch : Union [ str , None ] = environ .get ('SAUCE_VISUAL_BRANCH' ),
47
+ default_branch : Union [ str , None ] = environ .get ('SAUCE_VISUAL_DEFAULT_BRANCH' ),
48
+ custom_id : Union [ str , None ] = environ .get ('SAUCE_VISUAL_CUSTOM_ID' ),
49
+ keep_alive_timeout : Union [ int , None ] = None
45
50
):
46
51
query = gql (
47
52
# language=GraphQL
@@ -78,6 +83,7 @@ def create_build(
78
83
}
79
84
build = self .client .execute (query , variable_values = values )
80
85
self .build_id = build ['createBuild' ]['id' ]
86
+ self .build_url = build ['createBuild' ]['url' ]
81
87
return build
82
88
83
89
def finish_build (self ):
@@ -95,7 +101,6 @@ def finish_build(self):
95
101
)
96
102
values = {"id" : self .build_id }
97
103
self .meta_cache .clear ()
98
- self .build_id = None
99
104
return self .client .execute (query , variable_values = values )
100
105
101
106
def get_selenium_metadata (self , session_id : str ) -> str :
@@ -137,12 +142,13 @@ def create_snapshot_from_webdriver(
137
142
self ,
138
143
name : str ,
139
144
session_id : str ,
140
- test_name : str = None ,
141
- suite_name : str = None ,
145
+ test_name : Union [ str , None ] = None ,
146
+ suite_name : Union [ str , None ] = None ,
142
147
capture_dom : bool = False ,
143
- clip_selector : str = None ,
144
- ignore_regions : List [IgnoreRegion ] = None ,
145
- full_page_config : FullPageConfig = None ,
148
+ clip_selector : Union [str , None ] = None ,
149
+ ignore_regions : Union [List [IgnoreRegion ], None ] = None ,
150
+ full_page_config : Union [FullPageConfig , None ] = None ,
151
+ diffing_method : DiffingMethod = DiffingMethod .SIMPLE ,
146
152
):
147
153
query = gql (
148
154
# language=GraphQL
@@ -158,6 +164,7 @@ def create_snapshot_from_webdriver(
158
164
$clipSelector: String,
159
165
$ignoreRegions: [RegionIn!],
160
166
$fullPageConfig: FullPageConfigIn,
167
+ $diffingMethod: DiffingMethod,
161
168
) {
162
169
createSnapshotFromWebDriver(input: {
163
170
name: $name,
@@ -170,6 +177,7 @@ def create_snapshot_from_webdriver(
170
177
clipSelector: $clipSelector,
171
178
ignoreRegions: $ignoreRegions,
172
179
fullPageConfig: $fullPageConfig,
180
+ diffingMethod: $diffingMethod,
173
181
}){
174
182
id
175
183
}
@@ -191,5 +199,71 @@ def create_snapshot_from_webdriver(
191
199
"delayAfterScrollMs" : full_page_config .get ('delay_after_scroll_ms' ),
192
200
"hideAfterFirstScroll" : full_page_config .get ('hide_after_first_scroll' ),
193
201
} if full_page_config is not None else None ,
202
+ "diffingMethod" : (diffing_method or DiffingMethod .SIMPLE ).value ,
194
203
}
195
204
return self .client .execute (query , variable_values = values )
205
+
206
+ def get_build_status (
207
+ self ,
208
+ wait : bool = True ,
209
+ timeout : int = 60 ,
210
+ ):
211
+ query = gql (
212
+ # language=GraphQL
213
+ """
214
+ query buildStatus($buildId: UUID!) {
215
+ result: build(id: $buildId) {
216
+ id
217
+ name
218
+ url
219
+ mode
220
+ status
221
+ unapprovedCount: diffCountExtended(input: { status: UNAPPROVED })
222
+ approvedCount: diffCountExtended(input: { status: APPROVED })
223
+ rejectedCount: diffCountExtended(input: { status: REJECTED })
224
+ equalCount: diffCountExtended(input: { status: EQUAL })
225
+ erroredCount: diffCountExtended(input: { status: ERRORED })
226
+ queuedCount: diffCountExtended(input: { status: QUEUED })
227
+ }
228
+ }
229
+ """
230
+ )
231
+ values = {
232
+ "buildId" : self .build_id ,
233
+ }
234
+
235
+ if not wait :
236
+ return self .client .execute (query , variable_values = values )
237
+
238
+ cutoff_time = datetime .now () + timedelta (seconds = timeout )
239
+ build = None
240
+ result = None
241
+
242
+ while build is None and datetime .now () < cutoff_time :
243
+ result = self .client .execute (query , variable_values = values )
244
+
245
+ if result ['result' ] is None :
246
+ raise ValueError (
247
+ 'Sauce Visual build has been deleted or you do not have access to view it.'
248
+ )
249
+
250
+ if result ['result' ]['status' ] != BuildStatus .RUNNING .value :
251
+ build = result
252
+ else :
253
+ sleep (min (10 , timeout ))
254
+
255
+ # Return the successful build if available, else, the last run
256
+ return build if build is not None else result
257
+
258
+ def get_build_link (self ) -> str :
259
+ """
260
+ Get the dashboard build link for viewing the current build on Sauce Labs.
261
+ :return:
262
+ """
263
+ return self .build_url
264
+
265
+ def get_build_created_link (self ) -> str :
266
+ return f'Sauce Labs Visual build created:\t { self .get_build_link ()} '
267
+
268
+ def get_build_finished_link (self ) -> str :
269
+ return f'Sauce Labs Visual build finished:\t { self .get_build_link ()} '
0 commit comments