3
3
import static com .saucelabs .visual .utils .EnvironmentVariables .isNotBlank ;
4
4
import static com .saucelabs .visual .utils .EnvironmentVariables .valueOrDefault ;
5
5
6
+ import com .saucelabs .visual .exception .InvalidIgnoreSelectorException ;
7
+ import com .saucelabs .visual .exception .InvalidVisualRegionException ;
8
+ import com .saucelabs .visual .exception .InvalidWebElementException ;
6
9
import com .saucelabs .visual .exception .VisualApiException ;
7
10
import com .saucelabs .visual .graphql .*;
8
11
import com .saucelabs .visual .graphql .type .*;
@@ -153,6 +156,7 @@ public VisualApi build() {
153
156
}
154
157
155
158
private final GraphQLClient client ;
159
+ private final BulkDriverHelper bulkDriverHelper ;
156
160
157
161
private final VisualBuild build ;
158
162
private final String jobId ;
@@ -170,7 +174,7 @@ public VisualApi build() {
170
174
/**
171
175
* Creates a VisualApi instance for a given Visual Backend {@link DataCenter}
172
176
*
173
- * @param driver The {@link org.openqa.selenium. WebDriver} instance where the tests should run at
177
+ * @param driver The {@link WebDriver} instance where the tests should run at
174
178
* @param username SauceLabs username
175
179
* @param accessKey SauceLabs access key
176
180
*/
@@ -181,7 +185,7 @@ public VisualApi(RemoteWebDriver driver, String username, String accessKey) {
181
185
/**
182
186
* Creates a VisualApi instance for a given Visual Backend {@link DataCenter}
183
187
*
184
- * @param driver The {@link org.openqa.selenium. WebDriver} instance where the tests should run at
188
+ * @param driver The {@link WebDriver} instance where the tests should run at
185
189
* @param region Visual Backend Region. For available values, see: {@link DataCenter}
186
190
* @param username SauceLabs username
187
191
* @param accessKey SauceLabs access key
@@ -193,7 +197,7 @@ public VisualApi(RemoteWebDriver driver, DataCenter region, String username, Str
193
197
/**
194
198
* Creates a VisualApi instance with a custom backend URL
195
199
*
196
- * @param driver The {@link org.openqa.selenium. WebDriver} instance where the tests should run at
200
+ * @param driver The {@link WebDriver} instance where the tests should run at
197
201
* @param url Visual Backend URL
198
202
* @param username SauceLabs username
199
203
* @param accessKey SauceLabs access key
@@ -205,8 +209,7 @@ public VisualApi(RemoteWebDriver driver, String url, String username, String acc
205
209
/**
206
210
* Creates a VisualApi instance with a custom backend URL
207
211
*
208
- * @param driver The {@link org.openqa.selenium.WebDriver} instance where the tests should run
209
- * with
212
+ * @param driver The {@link WebDriver} instance where the tests should run with
210
213
* @param url Visual Backend URL
211
214
* @param username SauceLabs username
212
215
* @param accessKey SauceLabs access key
@@ -224,8 +227,7 @@ public VisualApi(
224
227
/**
225
228
* Creates a VisualApi instance with a custom backend URL
226
229
*
227
- * @param driver The {@link org.openqa.selenium.WebDriver} instance where the tests should run
228
- * with
230
+ * @param driver The {@link WebDriver} instance where the tests should run with
229
231
* @param url Visual Backend URL
230
232
* @param username SauceLabs username
231
233
* @param accessKey SauceLabs access key
@@ -265,6 +267,7 @@ private VisualApi(
265
267
this .build = VisualBuild .getBuildOnce (this , buildAttributes );
266
268
this .driver = driver ;
267
269
this .isSauceSession = isSauceSession ;
270
+ this .bulkDriverHelper = new BulkDriverHelper (driver );
268
271
refreshWebDriverSessionInfo ();
269
272
}
270
273
@@ -313,6 +316,7 @@ private VisualApi(Builder builder) {
313
316
this .driver = driver ;
314
317
this .client = new GraphQLClient (url , username , accessKey , requestConfig );
315
318
this .sessionMetadataBlob = sessionMetadataBlob ;
319
+ this .bulkDriverHelper = new BulkDriverHelper (driver );
316
320
}
317
321
318
322
/**
@@ -652,24 +656,14 @@ private String sauceVisualCheckLocal(String snapshotName, CheckOptions options)
652
656
SnapshotUpload uploadResult =
653
657
this .client .execute (mutation , CreateSnapshotUploadMutation .Data .class ).result ;
654
658
655
- // upload image
656
- this .client .upload (uploadResult .getImageUploadUrl (), screenshot , "image/png" );
657
-
658
659
// add ignore regions
659
660
List <RegionIn > ignoreRegions = extractIgnoreList (options );
661
+ ignoreRegions .addAll (
662
+ extractElementsToIgnoreRegions (
663
+ Optional .ofNullable (options .getIgnoreElements ()).orElse (Collections .emptyList ())));
664
+ ignoreRegions .addAll (extractIgnoreSelectors (options ));
660
665
661
- for (WebElement element : options .getIgnoreElements ()) {
662
- RegionIn ignoreRegion = VisualRegion .ignoreChangesFor (element ).toRegionIn ();
663
- ignoreRegions .add (ignoreRegion );
664
- }
665
-
666
- for (IgnoreSelectorIn selector : options .getIgnoreSelectors ()) {
667
- VisualRegion region = getIgnoreRegionFromSelector (selector );
668
- if (region != null ) {
669
- ignoreRegions .add (region .toRegionIn ());
670
- }
671
- }
672
-
666
+ // make regions relative to viewport
673
667
List <RegionIn > visibleIgnoreRegions = new ArrayList <>();
674
668
for (RegionIn region : ignoreRegions ) {
675
669
Rectangle regionRect =
@@ -686,6 +680,9 @@ private String sauceVisualCheckLocal(String snapshotName, CheckOptions options)
686
680
}
687
681
}
688
682
683
+ // upload image
684
+ this .client .upload (uploadResult .getImageUploadUrl (), screenshot , "image/png" );
685
+
689
686
// upload dom if present / enabled
690
687
Boolean shouldCaptureDom = Optional .ofNullable (options .getCaptureDom ()).orElse (this .captureDom );
691
688
if (shouldCaptureDom != null && shouldCaptureDom ) {
@@ -790,22 +787,6 @@ private WebElement getClipElement(CheckOptions checkOptions) {
790
787
return null ;
791
788
}
792
789
793
- private VisualRegion getIgnoreRegionFromSelector (IgnoreSelectorIn ignoreSelector ) {
794
- SelectorIn selector = ignoreSelector .getSelector ();
795
- By bySelector ;
796
-
797
- switch (selector .getType ()) {
798
- case XPATH :
799
- bySelector = By .xpath (selector .getValue ());
800
- break ;
801
- default :
802
- return null ;
803
- }
804
-
805
- WebElement element = driver .findElement (bySelector );
806
- return new VisualRegion (element , ignoreSelector .getDiffingOptions ());
807
- }
808
-
809
790
private static DiffingMethod toDiffingMethod (CheckOptions options ) {
810
791
if (options == null || options .getDiffingMethod () == null ) {
811
792
return DiffingMethod .BALANCED ;
@@ -887,33 +868,58 @@ private List<RegionIn> extractIgnoreList(CheckOptions options) {
887
868
}
888
869
889
870
List <IgnoreRegion > ignoredRegions =
890
- options .getIgnoreRegions () == null ? Arrays . asList () : options .getIgnoreRegions ();
871
+ options .getIgnoreRegions () == null ? Collections . emptyList () : options .getIgnoreRegions ();
891
872
892
873
List <VisualRegion > visualRegions =
893
- options .getRegions () == null ? Arrays . asList () : options .getRegions ();
874
+ options .getRegions () == null ? Collections . emptyList () : options .getRegions ();
894
875
895
- List <RegionIn > result = new ArrayList <>();
896
- for ( int i = 0 ; i < ignoredRegions .size (); i ++) {
897
- IgnoreRegion ignoreRegion = ignoredRegions . get ( i );
898
- if ( validate ( ignoreRegion ) == null ) {
899
- throw new VisualApiException ( "options. ignoreRegion[" + i + "] is an invalid ignore region" );
900
- }
901
- result . add (
902
- VisualRegion . ignoreChangesFor (
903
- ignoreRegion . getName (),
904
- ignoreRegion . getX (),
905
- ignoreRegion . getY (),
906
- ignoreRegion . getWidth (),
907
- ignoreRegion . getHeight ())
908
- . toRegionIn () );
909
- }
910
- for ( int i = 0 ; i < visualRegions . size (); i ++) {
911
- VisualRegion region = visualRegions . get ( i );
876
+ List <VisualRegion > allVisualRegions =
877
+ new ArrayList <>( ignoredRegions .size () + visualRegions . size ());
878
+ allVisualRegions . addAll ( visualRegions );
879
+
880
+ for ( IgnoreRegion ignoreRegion : ignoredRegions ) {
881
+ allVisualRegions . add ( new VisualRegion ( ignoreRegion ));
882
+ }
883
+
884
+ return extractRegions ( allVisualRegions );
885
+ }
886
+
887
+ private List < RegionIn > extractRegions ( List < VisualRegion > regions ) {
888
+ List < RegionIn > result = new ArrayList <>( regions . size ());
889
+ List < WebElement > bulkWebElements = new ArrayList <>( );
890
+ List < VisualRegion > bulkRegions = new ArrayList <>();
891
+
892
+ for ( VisualRegion region : regions ) {
912
893
if (validate (region ) == null ) {
913
- throw new VisualApiException ("options.region[" + i + "] is an invalid visual region" );
894
+ throw new InvalidVisualRegionException (region , "Visual region is invalid" );
895
+ }
896
+
897
+ WebElement element = region .getElement ();
898
+ if (element != null ) {
899
+ bulkWebElements .add (element );
900
+ bulkRegions .add (region );
901
+ } else {
902
+ result .add (region .toRegionIn ());
914
903
}
915
- result .add (region .toRegionIn ());
916
904
}
905
+
906
+ List <Boolean > bulkIsDisplayed = bulkDriverHelper .areDisplayed (bulkWebElements );
907
+ for (int i = 0 ; i < bulkIsDisplayed .size (); i ++) {
908
+ VisualRegion region = bulkRegions .get (i );
909
+ Boolean isDisplayed = bulkIsDisplayed .get (i );
910
+ if (!isDisplayed ) {
911
+ throw new InvalidVisualRegionException (
912
+ region , "Visual region's web element does not exist (yet)" );
913
+ }
914
+ }
915
+
916
+ List <Rectangle > bulkRectangles = bulkDriverHelper .getRects (bulkWebElements );
917
+ for (int i = 0 ; i < bulkRectangles .size (); i ++) {
918
+ VisualRegion region = bulkRegions .get (i );
919
+ Rectangle rectangle = bulkRectangles .get (i );
920
+ result .add (VisualRegion .ignoreChangesFor (region .getName (), rectangle ).toRegionIn ());
921
+ }
922
+
917
923
return result ;
918
924
}
919
925
@@ -924,31 +930,89 @@ private List<ElementIn> extractIgnoreElements(CheckOptions options) {
924
930
: Arrays .asList ();
925
931
926
932
List <ElementIn > result = new ArrayList <>();
927
- for (int i = 0 ; i < ignoredElements .size (); i ++) {
933
+
934
+ List <Boolean > bulkIsDisplayed = bulkDriverHelper .areDisplayed (ignoredElements );
935
+ for (int i = 0 ; i < bulkIsDisplayed .size (); i ++) {
928
936
WebElement element = ignoredElements .get (i );
929
- if (validate (element ) == null ) {
930
- throw new VisualApiException ("options.ignoreElement[" + i + "] does not exist (yet)" );
937
+ Boolean isDisplayed = bulkIsDisplayed .get (i );
938
+ if (!isDisplayed ) {
939
+ throw new InvalidWebElementException (element , "Web element does not exist (yet)" );
931
940
}
941
+
932
942
result .add (VisualRegion .ignoreChangesFor (element ).toElementIn ());
933
943
}
944
+
934
945
return result ;
935
946
}
936
947
937
- private WebElement validate (WebElement element ) {
938
- if (element instanceof RemoteWebElement && element .isDisplayed ()) {
939
- return element ;
948
+ private List <RegionIn > extractElementsToIgnoreRegions (List <WebElement > elements ) {
949
+ List <RegionIn > result = new ArrayList <>();
950
+
951
+ List <Boolean > bulkIsDisplayed = bulkDriverHelper .areDisplayed (elements );
952
+ for (int i = 0 ; i < bulkIsDisplayed .size (); i ++) {
953
+ WebElement element = elements .get (i );
954
+ Boolean isDisplayed = bulkIsDisplayed .get (i );
955
+ if (!isDisplayed ) {
956
+ throw new InvalidWebElementException (element , "Web element does not exist (yet)" );
957
+ }
940
958
}
941
- return null ;
959
+
960
+ List <Rectangle > bulkRectangles = bulkDriverHelper .getRects (elements );
961
+ for (Rectangle rectangle : bulkRectangles ) {
962
+ result .add (VisualRegion .ignoreChangesFor (rectangle ).toRegionIn ());
963
+ }
964
+
965
+ return result ;
942
966
}
943
967
944
- private IgnoreRegion validate (IgnoreRegion region ) {
945
- if (region == null ) {
946
- return null ;
968
+ private List <RegionIn > extractIgnoreSelectors (CheckOptions options ) {
969
+ List <IgnoreSelectorIn > selectors =
970
+ options != null && options .getIgnoreSelectors () != null
971
+ ? options .getIgnoreSelectors ()
972
+ : Arrays .asList ();
973
+
974
+ List <List <WebElement >> elementLists =
975
+ bulkDriverHelper .resolveElements (
976
+ selectors .stream ().map (IgnoreSelectorIn ::getSelector ).collect (Collectors .toList ()));
977
+
978
+ for (int i = 0 ; i < selectors .size (); i ++) {
979
+ List <WebElement > elements = elementLists .get (i );
980
+ IgnoreSelectorIn selector = selectors .get (i );
981
+ if (elements == null || elements .isEmpty ()) {
982
+ throw new InvalidIgnoreSelectorException (selector , "Web element does not exist" );
983
+ }
947
984
}
948
- if (region .getHeight () <= 0 || region .getWidth () <= 0 ) {
949
- return null ;
985
+
986
+ List <RegionIn > result = new ArrayList <>();
987
+
988
+ List <RegionIn > flatRegions =
989
+ extractElementsToIgnoreRegions (
990
+ elementLists .stream ().flatMap (List ::stream ).collect (Collectors .toList ()));
991
+
992
+ for (int listIndex = 0 , regionIndex = 0 ; listIndex < elementLists .size (); listIndex ++) {
993
+ IgnoreSelectorIn selector = selectors .get (listIndex );
994
+ List <WebElement > elements = elementLists .get (listIndex );
995
+ List <RegionIn > regions = flatRegions .subList (regionIndex , regionIndex + elements .size ());
996
+
997
+ result .addAll (
998
+ regions .stream ()
999
+ .map (
1000
+ region ->
1001
+ new VisualRegion (
1002
+ new IgnoreRegion (
1003
+ region .getName (),
1004
+ region .getX (),
1005
+ region .getY (),
1006
+ region .getWidth (),
1007
+ region .getHeight ()),
1008
+ selector .getDiffingOptions ())
1009
+ .toRegionIn ())
1010
+ .collect (Collectors .toList ()));
1011
+
1012
+ regionIndex += regions .size ();
950
1013
}
951
- return region ;
1014
+
1015
+ return result ;
952
1016
}
953
1017
954
1018
private VisualRegion validate (VisualRegion region ) {
@@ -958,10 +1022,6 @@ private VisualRegion validate(VisualRegion region) {
958
1022
if (0 < region .getHeight () * region .getWidth ()) {
959
1023
return region ;
960
1024
}
961
- WebElement ele = region .getElement ();
962
- if (ele != null && ele .isDisplayed () && ele .getRect () != null ) {
963
- return region ;
964
- }
965
1025
return null ;
966
1026
}
967
1027
0 commit comments