appium-webdriveragent 16.13.4 → 16.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [16.13.6](https://github.com/appium/WebDriverAgent/compare/v16.13.5...v16.13.6) (2026-09-30)
2
+
3
+ ### Bug Fixes
4
+
5
+ * restore stability wait settings when quiescence throws ([#1281](https://github.com/appium/WebDriverAgent/issues/1281)) ([bcc434c](https://github.com/appium/WebDriverAgent/commit/bcc434c08127c370c0cb05ce1dc9f4cea9f98559))
6
+
7
+ ## [16.13.5](https://github.com/appium/WebDriverAgent/compare/v16.13.4...v16.13.5) (2026-09-30)
8
+
9
+ ### Performance Improvements
10
+
11
+ * stream page-source XML with exception-safe resource cleanup ([#1276](https://github.com/appium/WebDriverAgent/issues/1276)) ([4e8639b](https://github.com/appium/WebDriverAgent/commit/4e8639bf73f962591e3e7b3be211f289a19ebf84))
12
+
1
13
  ## [16.13.4](https://github.com/appium/WebDriverAgent/compare/v16.13.3...v16.13.4) (2026-09-30)
2
14
 
3
15
  ### Performance Improvements
@@ -141,18 +141,21 @@
141
141
  return;
142
142
  }
143
143
 
144
- NSTimeInterval previousTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
145
- BOOL previousQuiescence = self.application.fb_shouldWaitForQuiescence;
146
- FBConfiguration.sharedInstance.waitForIdleTimeout = timeout;
147
- if (!previousQuiescence) {
148
- self.application.fb_shouldWaitForQuiescence = YES;
149
- }
150
- [[[self.application applicationImpl] currentProcess]
151
- fb_waitForQuiescenceIncludingAnimationsIdle:YES];
152
- if (previousQuiescence != self.application.fb_shouldWaitForQuiescence) {
153
- self.application.fb_shouldWaitForQuiescence = previousQuiescence;
144
+ XCUIApplication *application = self.application;
145
+ // Recursive synchronization also covers nested stability checks on this thread.
146
+ @synchronized (FBConfiguration.sharedInstance) {
147
+ NSTimeInterval previousTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
148
+ BOOL previousQuiescence = application.fb_shouldWaitForQuiescence;
149
+ @try {
150
+ FBConfiguration.sharedInstance.waitForIdleTimeout = timeout;
151
+ application.fb_shouldWaitForQuiescence = YES;
152
+ [[[application applicationImpl] currentProcess]
153
+ fb_waitForQuiescenceIncludingAnimationsIdle:YES];
154
+ } @finally {
155
+ application.fb_shouldWaitForQuiescence = previousQuiescence;
156
+ FBConfiguration.sharedInstance.waitForIdleTimeout = previousTimeout;
157
+ }
154
158
  }
155
- FBConfiguration.sharedInstance.waitForIdleTimeout = previousTimeout;
156
159
  }
157
160
 
158
161
  - (void)fb_raiseStaleElementExceptionWithError:(NSError *)error __attribute__((noreturn))
@@ -15,11 +15,11 @@
15
15
  <key>CFBundlePackageType</key>
16
16
  <string>FMWK</string>
17
17
  <key>CFBundleShortVersionString</key>
18
- <string>16.13.4</string>
18
+ <string>16.13.6</string>
19
19
  <key>CFBundleSignature</key>
20
20
  <string>????</string>
21
21
  <key>CFBundleVersion</key>
22
- <string>16.13.4</string>
22
+ <string>16.13.6</string>
23
23
  <key>NSPrincipalClass</key>
24
24
  <string/>
25
25
  </dict>
@@ -175,154 +175,175 @@ static NSString *const topNodeIndexPath = @"top";
175
175
  + (nullable NSString *)xmlStringWithRootElement:(id<FBElement>)root
176
176
  options:(nullable FBXMLGenerationOptions *)options
177
177
  {
178
- xmlDocPtr doc;
179
- xmlTextWriterPtr writer = xmlNewTextWriterDoc(&doc, 0);
180
- int rc = xmlTextWriterStartDocument(writer, NULL, _UTF8Encoding, NULL);
181
- if (rc < 0) {
182
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartDocument. Error code: %d", rc];
183
- } else {
184
- BOOL hasScope = nil != options.scope && [options.scope length] > 0;
185
- if (hasScope) {
186
- rc = xmlTextWriterStartElement(writer,
187
- (xmlChar *)[[self safeXmlStringWithString:options.scope] UTF8String]);
188
- if (rc < 0) {
189
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartElement for the tag value '%@'. Error code: %d", options.scope, rc];
190
- }
178
+ xmlBufferPtr buffer = NULL;
179
+ xmlTextWriterPtr writer = NULL;
180
+ @try {
181
+ buffer = xmlBufferCreate();
182
+ if (NULL == buffer) {
183
+ [FBLogger log:@"Failed to invoke libxml2>xmlBufferCreate"];
184
+ return nil;
191
185
  }
192
-
193
- if (rc >= 0) {
194
- [self waitUntilStableWithElement:root];
195
- // If 'includeHittableInPageSource' setting is enabled, then use native snapshots
196
- // to calculate a more accurate value for the 'hittable' attribute.
197
- rc = [self xmlRepresentationWithRootElement:[self snapshotWithRoot:root
198
- useNative:FBConfiguration.sharedInstance.includeHittableInPageSource]
199
- writer:writer
200
- elementStore:nil
201
- query:nil
202
- excludingAttributes:options.excludedAttributes];
186
+ writer = xmlNewTextWriterMemory(buffer, 0);
187
+ if (NULL == writer) {
188
+ [FBLogger log:@"Failed to invoke libxml2>xmlNewTextWriterMemory"];
189
+ return nil;
203
190
  }
191
+ xmlTextWriterSetIndent(writer, 1);
192
+ xmlTextWriterSetIndentString(writer, BAD_CAST " ");
193
+ int rc = xmlTextWriterStartDocument(writer, NULL, _UTF8Encoding, NULL);
194
+ if (rc < 0) {
195
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartDocument. Error code: %d", rc];
196
+ } else {
197
+ BOOL hasScope = nil != options.scope && [options.scope length] > 0;
198
+ if (hasScope) {
199
+ rc = xmlTextWriterStartElement(writer,
200
+ (xmlChar *)[[self safeXmlStringWithString:options.scope] UTF8String]);
201
+ if (rc < 0) {
202
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartElement for the tag value '%@'. Error code: %d", options.scope, rc];
203
+ }
204
+ }
204
205
 
205
- if (rc >= 0 && hasScope) {
206
- rc = xmlTextWriterEndElement(writer);
207
- if (rc < 0) {
208
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterEndElement. Error code: %d", rc];
206
+ if (rc >= 0) {
207
+ [self waitUntilStableWithElement:root];
208
+ // If 'includeHittableInPageSource' setting is enabled, then use native snapshots
209
+ // to calculate a more accurate value for the 'hittable' attribute.
210
+ rc = [self xmlRepresentationWithRootElement:[self snapshotWithRoot:root
211
+ useNative:FBConfiguration.sharedInstance.includeHittableInPageSource]
212
+ writer:writer
213
+ elementStore:nil
214
+ query:nil
215
+ excludingAttributes:options.excludedAttributes];
209
216
  }
210
- }
211
217
 
212
- if (rc >= 0) {
213
- rc = xmlTextWriterEndDocument(writer);
214
- if (rc < 0) {
215
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlXPathNewContext. Error code: %d", rc];
218
+ if (rc >= 0 && hasScope) {
219
+ rc = xmlTextWriterEndElement(writer);
220
+ if (rc < 0) {
221
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterEndElement. Error code: %d", rc];
222
+ }
223
+ }
224
+
225
+ if (rc >= 0) {
226
+ rc = xmlTextWriterEndDocument(writer);
227
+ if (rc < 0) {
228
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterEndDocument. Error code: %d", rc];
229
+ }
216
230
  }
217
231
  }
232
+ if (rc < 0) {
233
+ return nil;
234
+ }
235
+ return [[NSString alloc] initWithBytes:xmlBufferContent(buffer)
236
+ length:(NSUInteger)xmlBufferLength(buffer)
237
+ encoding:NSUTF8StringEncoding];
238
+ } @finally {
239
+ if (NULL != writer) {
240
+ xmlFreeTextWriter(writer);
241
+ }
242
+ if (NULL != buffer) {
243
+ xmlBufferFree(buffer);
244
+ }
218
245
  }
219
- if (rc < 0) {
220
- xmlFreeTextWriter(writer);
221
- xmlFreeDoc(doc);
222
- return nil;
223
- }
224
- int buffersize;
225
- xmlChar *xmlbuff;
226
- xmlDocDumpFormatMemory(doc, &xmlbuff, &buffersize, 1);
227
- xmlFreeTextWriter(writer);
228
- xmlFreeDoc(doc);
229
- NSString *result = [NSString stringWithCString:(const char *)xmlbuff encoding:NSUTF8StringEncoding];
230
- xmlFree(xmlbuff);
231
- return result;
232
246
  }
233
247
 
234
248
  + (NSArray<id<FBXCElementSnapshot>> *)matchesWithRootElement:(id<FBElement>)root
235
249
  forQuery:(NSString *)xpathQuery
236
250
  {
237
- xmlDocPtr doc;
238
-
239
- xmlTextWriterPtr writer = xmlNewTextWriterDoc(&doc, 0);
240
- if (NULL == writer) {
241
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlNewTextWriterDoc for XPath query \"%@\"", xpathQuery];
242
- return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
243
- }
244
- NSMutableDictionary *elementStore = [NSMutableDictionary dictionary];
245
- int rc = xmlTextWriterStartDocument(writer, NULL, _UTF8Encoding, NULL);
246
- id<FBXCElementSnapshot> lookupScopeSnapshot = nil;
247
- id<FBXCElementSnapshot> contextRootSnapshot = nil;
248
- BOOL useNativeSnapshot = nil == xpathQuery
249
- ? NO
250
- : [[self.class elementAttributesWithXPathQuery:xpathQuery] containsObject:FBHittableAttribute.class];
251
- if (rc < 0) {
252
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartDocument. Error code: %d", rc];
253
- } else {
254
- [self waitUntilStableWithElement:root];
255
- if (FBConfiguration.sharedInstance.limitXpathContextScope) {
256
- lookupScopeSnapshot = [self snapshotWithRoot:root useNative:useNativeSnapshot];
251
+ xmlDocPtr doc = NULL;
252
+ xmlTextWriterPtr writer = NULL;
253
+ xmlXPathObjectPtr contextNodeQueryResult = NULL;
254
+ xmlXPathObjectPtr queryResult = NULL;
255
+ @try {
256
+ writer = xmlNewTextWriterDoc(&doc, 0);
257
+ if (NULL == writer) {
258
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlNewTextWriterDoc for XPath query \"%@\"", xpathQuery];
259
+ return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
260
+ }
261
+ NSMutableDictionary *elementStore = [NSMutableDictionary dictionary];
262
+ int rc = xmlTextWriterStartDocument(writer, NULL, _UTF8Encoding, NULL);
263
+ id<FBXCElementSnapshot> lookupScopeSnapshot = nil;
264
+ id<FBXCElementSnapshot> contextRootSnapshot = nil;
265
+ BOOL useNativeSnapshot = nil == xpathQuery
266
+ ? NO
267
+ : [[self.class elementAttributesWithXPathQuery:xpathQuery] containsObject:FBHittableAttribute.class];
268
+ if (rc < 0) {
269
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterStartDocument. Error code: %d", rc];
257
270
  } else {
258
- if ([root isKindOfClass:XCUIElement.class]) {
259
- lookupScopeSnapshot = [self snapshotWithRoot:[(XCUIElement *)root application]
260
- useNative:useNativeSnapshot];
261
- // root.lastSnapshot may be stale leftover from an unrelated earlier command.
262
- contextRootSnapshot = [root isKindOfClass:XCUIApplication.class]
263
- ? nil
264
- : ([(XCUIElement *)root fb_cachedSnapshot] ?: [self snapshotWithRoot:(XCUIElement *)root
265
- useNative:useNativeSnapshot]);
271
+ [self waitUntilStableWithElement:root];
272
+ if (FBConfiguration.sharedInstance.limitXpathContextScope) {
273
+ lookupScopeSnapshot = [self snapshotWithRoot:root useNative:useNativeSnapshot];
266
274
  } else {
267
- lookupScopeSnapshot = (id<FBXCElementSnapshot>)root;
268
- contextRootSnapshot = nil == lookupScopeSnapshot.parent ? nil : (id<FBXCElementSnapshot>)root;
269
- while (nil != lookupScopeSnapshot.parent) {
270
- lookupScopeSnapshot = lookupScopeSnapshot.parent;
275
+ if ([root isKindOfClass:XCUIElement.class]) {
276
+ lookupScopeSnapshot = [self snapshotWithRoot:[(XCUIElement *)root application]
277
+ useNative:useNativeSnapshot];
278
+ // root.lastSnapshot may be stale leftover from an unrelated earlier command.
279
+ contextRootSnapshot = [root isKindOfClass:XCUIApplication.class]
280
+ ? nil
281
+ : ([(XCUIElement *)root fb_cachedSnapshot] ?: [self snapshotWithRoot:(XCUIElement *)root
282
+ useNative:useNativeSnapshot]);
283
+ } else {
284
+ lookupScopeSnapshot = (id<FBXCElementSnapshot>)root;
285
+ contextRootSnapshot = nil == lookupScopeSnapshot.parent ? nil : (id<FBXCElementSnapshot>)root;
286
+ while (nil != lookupScopeSnapshot.parent) {
287
+ lookupScopeSnapshot = lookupScopeSnapshot.parent;
288
+ }
271
289
  }
272
290
  }
273
- }
274
291
 
275
- rc = [self xmlRepresentationWithRootElement:lookupScopeSnapshot
276
- writer:writer
277
- elementStore:elementStore
278
- query:xpathQuery
279
- excludingAttributes:nil];
280
- if (rc >= 0) {
281
- rc = xmlTextWriterEndDocument(writer);
282
- if (rc < 0) {
283
- [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterEndDocument. Error code: %d", rc];
292
+ rc = [self xmlRepresentationWithRootElement:lookupScopeSnapshot
293
+ writer:writer
294
+ elementStore:elementStore
295
+ query:xpathQuery
296
+ excludingAttributes:nil];
297
+ if (rc >= 0) {
298
+ rc = xmlTextWriterEndDocument(writer);
299
+ if (rc < 0) {
300
+ [FBLogger logFmt:@"Failed to invoke libxml2>xmlTextWriterEndDocument. Error code: %d", rc];
301
+ }
284
302
  }
285
303
  }
286
- }
287
- if (rc < 0) {
288
- xmlFreeTextWriter(writer);
289
- xmlFreeDoc(doc);
290
- return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
291
- }
304
+ if (rc < 0) {
305
+ return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
306
+ }
292
307
 
293
- xmlXPathObjectPtr contextNodeQueryResult = [self matchNodeInDocument:doc
294
- elementStore:elementStore.copy
295
- forSnapshot:contextRootSnapshot];
296
- xmlNodePtr contextNode = NULL;
297
- if (NULL != contextNodeQueryResult) {
298
- xmlNodeSetPtr nodeSet = contextNodeQueryResult->nodesetval;
299
- if (!xmlXPathNodeSetIsEmpty(nodeSet)) {
300
- contextNode = nodeSet->nodeTab[0];
308
+ contextNodeQueryResult = [self matchNodeInDocument:doc
309
+ elementStore:elementStore.copy
310
+ forSnapshot:contextRootSnapshot];
311
+ xmlNodePtr contextNode = NULL;
312
+ if (NULL != contextNodeQueryResult) {
313
+ xmlNodeSetPtr nodeSet = contextNodeQueryResult->nodesetval;
314
+ if (!xmlXPathNodeSetIsEmpty(nodeSet)) {
315
+ contextNode = nodeSet->nodeTab[0];
316
+ }
317
+ }
318
+ NSString *evaluationError = nil;
319
+ queryResult = [self evaluate:xpathQuery
320
+ document:doc
321
+ contextNode:contextNode
322
+ errorMessage:&evaluationError];
323
+ if (NULL == queryResult) {
324
+ return [self throwException:FBInvalidXPathException forQuery:xpathQuery detail:evaluationError];
301
325
  }
302
- }
303
- NSString *evaluationError = nil;
304
- xmlXPathObjectPtr queryResult = [self evaluate:xpathQuery
305
- document:doc
306
- contextNode:contextNode
307
- errorMessage:&evaluationError];
308
- if (NULL != contextNodeQueryResult) {
309
- xmlXPathFreeObject(contextNodeQueryResult);
310
- }
311
- if (NULL == queryResult) {
312
- xmlFreeTextWriter(writer);
313
- xmlFreeDoc(doc);
314
- return [self throwException:FBInvalidXPathException forQuery:xpathQuery detail:evaluationError];
315
- }
316
326
 
317
- NSArray *matchingSnapshots = [self collectMatchingSnapshots:queryResult->nodesetval
318
- elementStore:elementStore];
319
- xmlXPathFreeObject(queryResult);
320
- xmlFreeTextWriter(writer);
321
- xmlFreeDoc(doc);
322
- if (nil == matchingSnapshots) {
323
- return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
327
+ NSArray *matchingSnapshots = [self collectMatchingSnapshots:queryResult->nodesetval
328
+ elementStore:elementStore];
329
+ if (nil == matchingSnapshots) {
330
+ return [self throwException:FBXPathQueryEvaluationException forQuery:xpathQuery];
331
+ }
332
+ return matchingSnapshots;
333
+ } @finally {
334
+ if (NULL != queryResult) {
335
+ xmlXPathFreeObject(queryResult);
336
+ }
337
+ if (NULL != contextNodeQueryResult) {
338
+ xmlXPathFreeObject(contextNodeQueryResult);
339
+ }
340
+ if (NULL != writer) {
341
+ xmlFreeTextWriter(writer);
342
+ }
343
+ if (NULL != doc) {
344
+ xmlFreeDoc(doc);
345
+ }
324
346
  }
325
- return matchingSnapshots;
326
347
  }
327
348
 
328
349
  + (NSArray *)collectMatchingSnapshots:(xmlNodeSetPtr)nodeSet
@@ -12,12 +12,52 @@
12
12
  #import "FBElement.h"
13
13
  #import "XCUIElementDouble.h"
14
14
  #import "FBElementUtils.h"
15
+ #import "FBConfiguration.h"
16
+ #import "XCUIElement+FBUtilities.h"
17
+ #import <objc/runtime.h>
18
+
19
+ @interface FBQuiescenceApplicationDouble : NSObject
20
+ @property (nonatomic) BOOL fb_shouldWaitForQuiescence;
21
+ @property (nonatomic) NSTimeInterval observedTimeout;
22
+ @end
23
+ @implementation FBQuiescenceApplicationDouble
24
+ - (id)applicationImpl { return self; }
25
+ - (id)currentProcess { return self; }
26
+ - (void)fb_waitForQuiescenceIncludingAnimationsIdle:(BOOL)includingAnimations
27
+ {
28
+ self.observedTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
29
+ @throw [NSException exceptionWithName:@"QuiescenceFailure" reason:@"test" userInfo:nil];
30
+ }
31
+ @end
15
32
 
16
33
  @interface FBElementUtilitiesTests : XCTestCase
17
34
  @end
18
35
 
19
36
  @implementation FBElementUtilitiesTests
20
37
 
38
+ - (void)testStabilityWaitRestoresSettingsAfterAnException
39
+ {
40
+ NSTimeInterval originalTimeout = FBConfiguration.sharedInstance.waitForIdleTimeout;
41
+ FBQuiescenceApplicationDouble *application = [FBQuiescenceApplicationDouble new];
42
+ XCUIElementDouble *element = [XCUIElementDouble new];
43
+ element.application = (id)application;
44
+ SEL selector = @selector(fb_waitUntilStableWithTimeout:);
45
+ #pragma clang diagnostic push
46
+ #pragma clang diagnostic ignored "-Wcast-function-type-strict"
47
+ void (*wait)(id, SEL, NSTimeInterval) = (void (*)(id, SEL, NSTimeInterval))
48
+ class_getMethodImplementation(XCUIElement.class, selector);
49
+ #pragma clang diagnostic pop
50
+ @try {
51
+ FBConfiguration.sharedInstance.waitForIdleTimeout = 17;
52
+ XCTAssertThrowsSpecificNamed(wait(element, selector, 2), NSException, @"QuiescenceFailure");
53
+ XCTAssertEqual(application.observedTimeout, 2);
54
+ XCTAssertFalse(application.fb_shouldWaitForQuiescence);
55
+ XCTAssertEqual(FBConfiguration.sharedInstance.waitForIdleTimeout, 17);
56
+ } @finally {
57
+ FBConfiguration.sharedInstance.waitForIdleTimeout = originalTimeout;
58
+ }
59
+ }
60
+
21
61
  - (void)testTypesFiltering {
22
62
  NSMutableArray *elements = [NSMutableArray new];
23
63
  XCUIElementDouble *el1 = [XCUIElementDouble new];
@@ -16,6 +16,33 @@
16
16
  #import "FBXCElementSnapshotWrapper+Helpers.h"
17
17
  #import "XCTestPrivateSymbols.h"
18
18
 
19
+ // libxml's node callbacks are thread-local; restore any existing callbacks after each probe.
20
+ static __thread NSUInteger FBReviewCreatedDocs;
21
+ static __thread NSUInteger FBReviewFreedDocs;
22
+ static __thread xmlRegisterNodeFunc FBReviewPreviousRegister;
23
+ static __thread xmlDeregisterNodeFunc FBReviewPreviousDeregister;
24
+
25
+ static void FBReviewRegisterNode(xmlNodePtr node)
26
+ {
27
+ if (node->type == XML_DOCUMENT_NODE) { FBReviewCreatedDocs++; }
28
+ if (NULL != FBReviewPreviousRegister) { FBReviewPreviousRegister(node); }
29
+ }
30
+
31
+ static void FBReviewDeregisterNode(xmlNodePtr node)
32
+ {
33
+ if (node->type == XML_DOCUMENT_NODE) { FBReviewFreedDocs++; }
34
+ if (NULL != FBReviewPreviousDeregister) { FBReviewPreviousDeregister(node); }
35
+ }
36
+
37
+ @interface FBThrowingLabelSnapshot : XCElementSnapshotDouble
38
+ @end
39
+ @implementation FBThrowingLabelSnapshot
40
+ - (NSString *)label
41
+ {
42
+ @throw [NSException exceptionWithName:@"SnapshotAttributeFailure" reason:@"test" userInfo:nil];
43
+ }
44
+ @end
45
+
19
46
  @interface FBTraversalSnapshot : XCElementSnapshotDouble
20
47
  @property (nonatomic, copy) NSArray *childSnapshots;
21
48
  @property (nonatomic, weak) FBTraversalSnapshot *parentSnapshot;
@@ -57,6 +84,44 @@
57
84
  }
58
85
  }
59
86
 
87
+ - (void)testDirectXmlWriterPreservesEscapingAndFormatting
88
+ {
89
+ XCElementSnapshotDouble *snapshot = [XCElementSnapshotDouble new];
90
+ snapshot.label = @"line one\nline two\t& < > \" йоло";
91
+ snapshot.value = @"value";
92
+ NSString *expected = [[self xmlStringWithElement:(id)snapshot xpathQuery:nil excludingAttributes:nil]
93
+ stringByReplacingOccurrencesOfString:@" private_indexPath=\"top\"" withString:@""];
94
+ NSString *actual = [FBXPath xmlStringWithRootElement:(id)snapshot options:nil];
95
+ XCTAssertEqualObjects(actual, expected);
96
+ }
97
+
98
+ - (void)testXmlDocumentsAreFreedWhenSnapshotAttributesThrow
99
+ {
100
+ for (NSNumber *isLookup in @[@NO, @YES]) {
101
+ FBReviewCreatedDocs = 0;
102
+ FBReviewFreedDocs = 0;
103
+ FBReviewPreviousRegister = xmlRegisterNodeDefault(FBReviewRegisterNode);
104
+ FBReviewPreviousDeregister = xmlDeregisterNodeDefault(FBReviewDeregisterNode);
105
+ @try {
106
+ id<FBElement> snapshot = (id)[FBThrowingLabelSnapshot new];
107
+ if (isLookup.boolValue) {
108
+ XCTAssertThrowsSpecificNamed([FBXPath matchesWithRootElement:snapshot forQuery:@"//*[@label]"],
109
+ NSException, @"SnapshotAttributeFailure");
110
+ } else {
111
+ XCTAssertThrowsSpecificNamed([FBXPath xmlStringWithRootElement:snapshot options:nil],
112
+ NSException, @"SnapshotAttributeFailure");
113
+ }
114
+ if (isLookup.boolValue) {
115
+ XCTAssertGreaterThan(FBReviewCreatedDocs, 0u);
116
+ }
117
+ XCTAssertEqual(FBReviewCreatedDocs, FBReviewFreedDocs);
118
+ } @finally {
119
+ xmlRegisterNodeDefault(FBReviewPreviousRegister);
120
+ xmlDeregisterNodeDefault(FBReviewPreviousDeregister);
121
+ }
122
+ }
123
+ }
124
+
60
125
  - (NSString *)xmlStringWithElement:(id<FBXCElementSnapshot>)snapshot
61
126
  xpathQuery:(nullable NSString *)query
62
127
  excludingAttributes:(nullable NSArray<NSString *> *)excludedAttributes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-webdriveragent",
3
- "version": "16.13.4",
3
+ "version": "16.13.6",
4
4
  "description": "Package bundling WebDriverAgent",
5
5
  "keywords": [
6
6
  "Appium",