pomwright 1.1.1 → 1.2.0

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,5 +1,588 @@
1
1
  # pomwright
2
2
 
3
+ ## 1.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#31](https://github.com/DyHex/POMWright/pull/31) [`bc29062`](https://github.com/DyHex/POMWright/commit/bc2906258a88acde1dd1479d7a000d036912f45a) Thanks [@DyHex](https://github.com/DyHex)! - # LocatorSchema Enhancements: `filter` Property, `.addFilter()`, Updated `.update()`, and Enhanced `getNestedLocator()`
8
+
9
+ ## Overview
10
+
11
+ This release introduces several key enhancements to the POMWright. These improvements aim to provide greater flexibility, maintainability, and type safety when constructing and managing locators in your Page Object Models (POMs). The main updates include:
12
+
13
+ - **New `filter` Property**: Extends filtering capabilities beyond the `locator` method.
14
+ - **New `.addFilter()` Method**: Facilitates dynamic addition of filters to locators.
15
+ - **Updated `.update()` Method**: Replaces deprecated `.update` and `.updates` methods with a more intuitive interface (none-breaking).
16
+ - **Enhanced `getNestedLocator()` Method**: Shifts from index-based to subPath-based indexing for better readability and maintainability.
17
+ - **Export of `LocatorSchemaWithoutPath`**: Enables partial or full reuse of locator schemas across different contexts.
18
+
19
+ ## Changes
20
+
21
+ ### 1. New `filter` Property for `LocatorSchema`
22
+
23
+ #### Purpose
24
+
25
+ The existing `locatorOptions` property in `LocatorSchema` is specific to the `locator` method, limiting its applicability. The newly introduced `filter` property extends filtering capabilities to all locator types (e.g., `role`, `label`, `placeholder`, `testid`, `id`, etc.), excluding `frameLocator`.
26
+
27
+ #### Behavior
28
+
29
+ - **Global Applicability**: Unlike `locatorOptions`, the `filter` property can be applied to various locator methods, enhancing versatility.
30
+ - **Priority Application**: When a `filter` is defined, it is always applied and will always be the first filter applied to the locator created from that LocatorSchema, the only exception is `locatorOptions` used with `locator`. This ensures consistent and prioritized filtering across different locator types.
31
+
32
+ #### Usage Examples
33
+
34
+ **Before: Using `locatorOptions` (Limited to `locator` method)**
35
+
36
+ ```typescript
37
+ this.locators.addSchema("main.products.radio@junior", {
38
+ locator: "radio",
39
+ locatorOptions: { hasText: "some text" },
40
+ locatorMethod: GetByMethod.locator,
41
+ });
42
+ ```
43
+
44
+ **After: Using `filter` (Applicable to Multiple Locator Methods)**
45
+
46
+ ```typescript
47
+ this.locators.addSchema("main.products.radio@junior", {
48
+ locator: "input",
49
+ filter: { hasText: "some text" },
50
+ locatorMethod: GetByMethod.locator,
51
+ });
52
+ ```
53
+
54
+ **With `role` Locator Method:**
55
+
56
+ ```typescript
57
+ this.locators.addSchema("main.products.radio@junior", {
58
+ role: "radio",
59
+ filter: { hasText: "some text" },
60
+ locatorMethod: GetByMethod.role,
61
+ });
62
+ ```
63
+
64
+ **Combined Example:**
65
+
66
+ ```typescript
67
+ this.locators.addSchema("main.subscription.form.item.section@header", {
68
+ role: "region",
69
+ roleOptions: { name: "Subscription Details" },
70
+ filter: { hasText: "Mobile 2 GB" },
71
+ locatorMethod: GetByMethod.role,
72
+ });
73
+ ```
74
+
75
+ #### Benefits
76
+
77
+ - **Enhanced Flexibility**: Apply default filters to various LocatorSchema.
78
+ - **Improved Locator Precision**: Chain multiple filters to narrow down locators based on complex criteria.
79
+ - **Backward Compatibility**: Existing `locatorOptions` works the same way as before, ensuring no disruption to current implementations.
80
+
81
+ ### 2. New `.addFilter()` Method for `.getLocatorSchema()`
82
+
83
+ #### Purpose
84
+
85
+ The `.addFilter()` method allows dynamic addition of filters to any part of the locator chain, enhancing flexibility in locator construction without modifying the original `LocatorSchema` at any point in a test.
86
+
87
+ #### Method Signature
88
+
89
+ ```typescript
90
+ .addFilter(
91
+ subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>,
92
+ filterData: FilterEntry
93
+ ): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>
94
+ ```
95
+
96
+ #### Parameters
97
+
98
+ - `subPath`: A valid sub-path within the `LocatorSchemaPath` argument to .getLocatorSchema().
99
+ - `filterData`: An object defining the filter criteria, which can include:
100
+ - `has?: Locator`
101
+ - `hasNot?: Locator`
102
+ - `hasText?: string | RegExp`
103
+ - `hasNotText?: string | RegExp`
104
+
105
+ #### Usage Examples
106
+
107
+ **Adding Filters to Specific Sub-Paths:**
108
+
109
+ ```typescript
110
+ const allCheckboxesForBrandFilters = await poc
111
+ .getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
112
+ .addFilter("main.products.searchControls.filterType", {
113
+ hasText: /Producer/i,
114
+ })
115
+ .getNestedLocator();
116
+ ```
117
+
118
+ **Chaining Multiple Filters:**
119
+
120
+ ```typescript
121
+ const refinedCheckboxes = await poc
122
+ .getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
123
+ .addFilter("main.products.searchControls.filterType", {
124
+ hasText: /Producer/i,
125
+ hasNotText: /discontinued/i,
126
+ })
127
+ .addFilter("main.products.searchControls.filterType.label", {
128
+ hasText: "Samsung",
129
+ })
130
+ .addFilter("main.products.searchControls.filterType.label", {
131
+ has: poc.page.getByRole("checkbox"),
132
+ })
133
+ .getNestedLocator();
134
+ ```
135
+
136
+ **Combining `filter` Property and `.addFilter()`:**
137
+
138
+ ```typescript
139
+ this.locators.addSchema("main.subscription.form.item.section@header", {
140
+ role: "region",
141
+ roleOptions: { name: "Subscription Details" },
142
+ filter: { hasText: "Mobile 2 GB" },
143
+ locatorMethod: GetByMethod.role,
144
+ });
145
+
146
+ const specificSection = await poc
147
+ .getLocatorSchema("main.subscription.form.item.section@header")
148
+ .addFilter("main.subscription.form.item.section@header", {
149
+ hasText: "Additional Services",
150
+ })
151
+ .getNestedLocator();
152
+ ```
153
+
154
+ #### Benefits
155
+
156
+ - **Dynamic Filtering**: Add or modify filters on-the-fly without altering the original schema definitions.
157
+ - **Chainability**: Easily chain multiple `.addFilter()` calls to build complex locator chains.
158
+ - **Error Handling**: Attempts to add filters to invalid sub-paths will throw descriptive errors (compile & run-time), ensuring only valid paths are modified.
159
+
160
+ ### 3. Updated `.update()` Method
161
+
162
+ #### Purpose
163
+
164
+ The existing `.update` and `.updates` methods are deprecated and will be removed in version 2.0.0. `.update` could only update the last LocatorSchema in the chain and `.updates` relied on index-based updates, which posed maintainability challenges, especially when renaming `LocatorSchemaPath` strings. The new `.update` method leverages valid `subPaths` of `LocatorSchemaPath` instead of indices, enhancing readability and reducing manual maintenance.
165
+
166
+ #### New Method Signature
167
+
168
+ ```typescript
169
+ .update(
170
+ subPath: SubPaths<LocatorSchemaPathType, LocatorSubstring>,
171
+ modifiedSchema: Partial<LocatorSchemaWithoutPath>
172
+ ): LocatorSchemaWithMethods<LocatorSchemaPathType, LocatorSubstring>
173
+ ```
174
+
175
+ #### Deprecation of Old Methods
176
+
177
+ - **Old `.update(updates: Partial<LocatorSchemaWithoutPath>): LocatorSchemaWithMethods`**
178
+ - **Old `.updates(indexedUpdates: { [index: number]: Partial<LocatorSchemaWithoutPath> | null }): LocatorSchemaWithMethods`**
179
+
180
+ These methods are marked as deprecated and will be removed in version 2.0.0.
181
+
182
+ #### Usage Examples
183
+
184
+ **Case A: Update the Last `LocatorSchema` in the Chain**
185
+
186
+ ```typescript
187
+ const editBtn = await profile
188
+ .getLocatorSchema("content.region.details.button.edit")
189
+ .update("content.region.details.button.edit", {
190
+ roleOptions: { name: "new accessibility name" },
191
+ })
192
+ .getNestedLocator();
193
+ ```
194
+
195
+ **Case B: Update a `LocatorSchema` Earlier in the Chain**
196
+
197
+ ```typescript
198
+ const editBtn = await profile
199
+ .getLocatorSchema("content.region.details.button.edit")
200
+ .update("content.region.details", {
201
+ roleOptions: { name: "new accessibility name" },
202
+ })
203
+ .getNestedLocator();
204
+ ```
205
+
206
+ **Case C: Update Multiple `LocatorSchema` in the Chain**
207
+
208
+ ```typescript
209
+ const editBtn = await profile
210
+ .getLocatorSchema("content.region.details.button.edit")
211
+ .update("content", { roleOptions: { name: "new accessibility name" } })
212
+ .update("content.region", {
213
+ roleOptions: { name: "new accessibility name" },
214
+ })
215
+ .update("content.region.details", {
216
+ roleOptions: { name: "new accessibility name" },
217
+ })
218
+ .update("content.region.details.button", {
219
+ roleOptions: { name: "new accessibility name" },
220
+ })
221
+ .update("content.region.details.button.edit", {
222
+ roleOptions: { name: "new accessibility name" },
223
+ })
224
+ .getNestedLocator();
225
+ ```
226
+
227
+ #### Benefits
228
+
229
+ - **Enhanced Readability**: Using `LocatorSchemaPath` strings makes the code more intuitive and easier to understand.
230
+ - **Reduced Maintenance**: Eliminates the need to manage index-based references, especially when `LocatorSchemaPath` strings are renamed or restructured.
231
+ - **Type Safety**: Leverages TypeScript's type system to ensure only valid `subPath` strings are used, enhancing developer experience with accurate Intellisense suggestions.
232
+
233
+ ### 4. Enhanced `getNestedLocator()` Method
234
+
235
+ #### Purpose
236
+
237
+ The `getNestedLocator()` method has been updated to utilize valid `subPath`'s of `LocatorSchemaPath` instead of numeric indices when specifying `.nth(n)` occurrences within a locator chain. The old index-based method is deprecated and will be removed in version 2.0.0.
238
+
239
+ #### New Method Signature
240
+
241
+ ```typescript
242
+ getNestedLocator(
243
+ subPathIndices?: { [K in SubPaths<LocatorSchemaPathType, LocatorSubstring>]: number | null }
244
+ ): Promise<Locator>
245
+ ```
246
+
247
+ #### Deprecation of Old Method
248
+
249
+ - **Old `getNestedLocator(indices?: { [key: number]: number | null }): Promise<Locator>`**
250
+
251
+ This method is marked as deprecated and will be removed in version 2.0.0.
252
+
253
+ #### Usage Examples
254
+
255
+ **Old Usage (Deprecated):**
256
+
257
+ ```typescript
258
+ const saveBtn = await profile.getNestedLocator(
259
+ "content.region.details.button.save",
260
+ { 4: 2 }
261
+ );
262
+
263
+ const editBtn = await profile
264
+ .getLocatorSchema("content.region.details.button.edit")
265
+ .getNestedLocator({ 2: index });
266
+ ```
267
+
268
+ **New Usage:**
269
+
270
+ ```typescript
271
+ const saveBtn = await profile.getNestedLocator(
272
+ "content.region.details.button.save",
273
+ {
274
+ "content.region.details.button.save": 2,
275
+ }
276
+ );
277
+
278
+ const editBtn = await profile
279
+ .getLocatorSchema("content.region.details.button.edit")
280
+ .getNestedLocator({ "content.region.details": index });
281
+ ```
282
+
283
+ #### Benefits
284
+
285
+ - **Improved Readability**: SubPath-based indexing is more intuitive and aligns with the hierarchical nature of locator paths.
286
+ - **Enhanced Maintainability**: Reduces confusion and manual updates when `LocatorSchemaPath` strings are modified.
287
+ - **Type Safety and Intellisense**: TypeScript provides accurate suggestions for valid `subPath` keys, enhancing the developer experience and reducing errors.
288
+
289
+ ### 5. Export of `LocatorSchemaWithoutPath`
290
+
291
+ #### Purpose
292
+
293
+ The `LocatorSchemaWithoutPath` type is now exported through `index.ts`, enabling full or partial reuse of `LocatorSchema` definitions across different `addSchema` calls within the same or different `initLocatorSchemas` functions.
294
+
295
+ #### Usage Example
296
+
297
+ ```typescript
298
+ import { GetByMethod, LocatorSchemaWithoutPath } from "pomwright";
299
+ import { missingInputError } from "@common/page-components/errors.locatorSchema.ts";
300
+
301
+ export type LocatorSchemaPath =
302
+ | "body"
303
+ | "body.main"
304
+ | "body.main.section"
305
+ | "body.main.section@products"
306
+ | "body.main.section@userInfo"
307
+ | "body.main.section@userInfo.input@email"
308
+ | "body.main.section@userInfo.inputError"
309
+ | "body.main.section@deliveryInfo";
310
+
311
+ export function initLocatorSchemas(
312
+ locators: GetLocatorBase<LocatorSchemaPath>
313
+ ) {
314
+ locators.addSchema("body", {
315
+ locator: "body",
316
+ locatorMethod: GetByMethod.locator,
317
+ });
318
+
319
+ locators.addSchema("body.main", {
320
+ locator: "main",
321
+ locatorMethod: GetByMethod.locator,
322
+ });
323
+
324
+ const region: LocatorSchemaWithoutPath = {
325
+ role: "region",
326
+ locatorMethod: GetByMethod.role,
327
+ };
328
+
329
+ locators.addSchema("body.main.section", {
330
+ ...region,
331
+ });
332
+
333
+ locators.addSchema("body.main.section@products", {
334
+ ...region,
335
+ roleOptions: { name: "Products" },
336
+ });
337
+
338
+ locators.addSchema("body.main.section@userInfo", {
339
+ ...region,
340
+ roleOptions: { name: "Contact Info" },
341
+ filter: { hasText: /e-mail/i },
342
+ });
343
+
344
+ locators.addSchema("body.main.section@userInfo.input@email", {
345
+ role: "textbox",
346
+ roleOptions: { name: "Input your e-mail" },
347
+ locatorMethod: GetByMethod.role,
348
+ });
349
+
350
+ locators.addSchema("body.main.section@userInfo.inputError", {
351
+ ...missingInputError,
352
+ });
353
+
354
+ locators.addSchema("body.main.section@deliveryInfo", {
355
+ ...region,
356
+ roleOptions: { name: "Delivery Info" },
357
+ });
358
+ }
359
+ ```
360
+
361
+ #### Benefits
362
+
363
+ - **Code Reusability**: Facilitates the reuse of common locator schema fragments across different contexts, reducing redundancy.
364
+ - **Modular Definitions**: Encourages a modular approach to defining locators, enhancing organization and scalability.
365
+
366
+ ## Deprecations
367
+
368
+ ### 1. Deprecated `.update` and `.updates` Methods
369
+
370
+ - **Old `.update(updates: Partial<LocatorSchemaWithoutPath>): LocatorSchemaWithMethods`**
371
+ - **Old `.updates(indexedUpdates: { [index: number]: Partial<LocatorSchemaWithoutPath> | null }): LocatorSchemaWithMethods`**
372
+
373
+ **Reason:**
374
+ `.updates` relied on index-based updates, which are prone to errors and require manual maintenance, especially when `LocatorSchemaPath` strings are renamed or restructured. While the old `.update` method could only update the last LocatorSchema in the path. Confusing with multiple methods instead of just one.
375
+
376
+ **Replacement:**
377
+ Use the new `.update(subPath, modifiedSchema)` method, which leverages `subpath`'s of valid `LocatorSchemaPath` strings for more intuitive and maintainable updates.
378
+
379
+ **Removal Schedule:**
380
+ These methods are deprecated and will be removed in version 2.0.0.
381
+
382
+ ### 2. Deprecated Old `getNestedLocator` Method
383
+
384
+ - **Old `getNestedLocator(indices?: { [key: number]: number | null }): Promise<Locator>`**
385
+
386
+ **Reason:**
387
+ Index-based indexing is less readable and requires manual updates when `LocatorSchemaPath` strings change.
388
+
389
+ **Replacement:**
390
+ Use the updated `getNestedLocator(subPathIndices?: { [K in SubPaths<LocatorSchemaPathType, LocatorSubstring>]: number | null }): Promise<Locator>` method, which utilizes `LocatorSchemaPath` strings for indexing.
391
+
392
+ **Removal Schedule:**
393
+ This method is deprecated and will be removed in version 2.0.0.
394
+
395
+ ## Migration Guide
396
+
397
+ ### Updating `.update` and `.updates` Methods
398
+
399
+ **Old Usage:**
400
+
401
+ ```typescript
402
+ const allCheckboxes = await poc
403
+ .getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
404
+ .updates({ 3: { locatorOptions: { hasText: /Producer/i } } })
405
+ .getNestedLocator();
406
+ ```
407
+
408
+ **New Usage:**
409
+
410
+ ```typescript
411
+ const allCheckboxes = await poc
412
+ .getLocatorSchema("main.products.searchControls.filterType.label.checkbox")
413
+ .update("main.products.searchControls.filterType", {
414
+ locatorOptions: { hasText: /Producer/i },
415
+ })
416
+ .getNestedLocator();
417
+ ```
418
+
419
+ ### Updating `getNestedLocator` Method
420
+
421
+ **Old Usage (Deprecated):**
422
+
423
+ ```typescript
424
+ const saveBtn = await profile.getNestedLocator(
425
+ "content.region.details.button.save",
426
+ { 4: 2 }
427
+ );
428
+
429
+ const editBtn = await profile
430
+ .getLocatorSchema("content.region.details.button.edit")
431
+ .getNestedLocator({ 2: index });
432
+ ```
433
+
434
+ **New Usage:**
435
+
436
+ ```typescript
437
+ const saveBtn = await profile.getNestedLocator(
438
+ "content.region.details.button.save",
439
+ {
440
+ "content.region.details.button.save": 2,
441
+ }
442
+ );
443
+
444
+ const editBtn = await profile
445
+ .getLocatorSchema("content.region.details.button.edit")
446
+ .getNestedLocator({ "content.region.details": index });
447
+ ```
448
+
449
+ ### Utilizing `LocatorSchemaPath` Instead of Indices
450
+
451
+ Transition from index-based to `LocatorSchemaPath`-based indexing to improve code readability and maintainability.
452
+
453
+ **Old Example:**
454
+
455
+ ```typescript
456
+ const allCheckboxes = await poc
457
+ .getLocatorSchema("main.form.item.checkbox")
458
+ .updates({ 3: { locatorOptions: { hasText: /Producer/i } } })
459
+ .getNestedLocator();
460
+ ```
461
+
462
+ **New Example:**
463
+
464
+ ```typescript
465
+ const allCheckboxes = await poc
466
+ .getLocatorSchema("main.form.item.checkbox")
467
+ .update("main.form.item", { locatorOptions: { hasText: /Producer/i } })
468
+ .getNestedLocator();
469
+ ```
470
+
471
+ ## Example in Context
472
+
473
+ ### Defining a LocatorSchema with `filter` and Using `.addFilter()`
474
+
475
+ ```typescript
476
+ // Defining LocatorSchemas
477
+ this.locators.addSchema("body.main.section@userInfo", {
478
+ role: "region",
479
+ roleOptions: { name: "Contact Info" },
480
+ filter: { hasText: /e-mail/i },
481
+ locatorMethod: GetByMethod.role,
482
+ });
483
+
484
+ // Dynamically adding additional filters using `.addFilter()`
485
+ const specificSection = await poc
486
+ .getLocatorSchema("body.main.section@userInfo")
487
+ .addFilter("body.main.section@userInfo", { hasText: "Additional Services" })
488
+ .getNestedLocator();
489
+ ```
490
+
491
+ ### Updating LocatorSchemas with the New `.update()` Method
492
+
493
+ ```typescript
494
+ const editBtn = await profile
495
+ .getLocatorSchema("content.region.details.button.edit")
496
+ .update("content.region.details.button.edit", {
497
+ roleOptions: { name: "new accessibility name" },
498
+ })
499
+ .getNestedLocator();
500
+ ```
501
+
502
+ ### Reusing LocatorSchemas with `LocatorSchemaWithoutPath`
503
+
504
+ ```typescript
505
+ import { GetByMethod, LocatorSchemaWithoutPath } from "pomwright";
506
+ import { missingInputError } from "@common/page-components/errors.locatorSchema.ts";
507
+
508
+ export type LocatorSchemaPath =
509
+ | "body"
510
+ | "body.main"
511
+ | "body.main.section"
512
+ | "body.main.section@products"
513
+ | "body.main.section@userInfo"
514
+ | "body.main.section@userInfo.input@email"
515
+ | "body.main.section@userInfo.inputError"
516
+ | "body.main.section@deliveryInfo";
517
+
518
+ export function initLocatorSchemas(
519
+ locators: GetLocatorBase<LocatorSchemaPath>
520
+ ) {
521
+ locators.addSchema("body", {
522
+ locator: "body",
523
+ locatorMethod: GetByMethod.locator,
524
+ });
525
+
526
+ locators.addSchema("body.main", {
527
+ locator: "main",
528
+ locatorMethod: GetByMethod.locator,
529
+ });
530
+
531
+ const region: LocatorSchemaWithoutPath = {
532
+ role: "region",
533
+ locatorMethod: GetByMethod.role,
534
+ };
535
+
536
+ locators.addSchema("body.main.section", {
537
+ ...region,
538
+ });
539
+
540
+ locators.addSchema("body.main.section@products", {
541
+ ...region,
542
+ roleOptions: { name: "Products" },
543
+ });
544
+
545
+ locators.addSchema("body.main.section@userInfo", {
546
+ ...region,
547
+ roleOptions: { name: "Contact Info" },
548
+ filter: { hasText: /e-mail/i },
549
+ });
550
+
551
+ locators.addSchema("body.main.section@userInfo.input@email", {
552
+ role: "textbox",
553
+ roleOptions: { name: "Input your e-mail" },
554
+ locatorMethod: GetByMethod.role,
555
+ });
556
+
557
+ locators.addSchema("body.main.section@userInfo.inputError", {
558
+ ...missingInputError,
559
+ });
560
+
561
+ locators.addSchema("body.main.section@deliveryInfo", {
562
+ ...region,
563
+ roleOptions: { name: "Delivery Info" },
564
+ });
565
+ }
566
+ ```
567
+
568
+ ## Notes
569
+
570
+ - **Filter Application Order:**
571
+ The `filter` property in `LocatorSchema` is always applied first, followed by any filters added via `.addFilter()`. This ensures that predefined filters have priority over dynamically added ones.
572
+
573
+ - **Exclusion of `frameLocator`:**
574
+ The `filter` property does not apply to `frameLocator` locators. If you need to filter within frames, use the appropriate Playwright frame methods.
575
+
576
+ - **Type Safety and Intellisense:**
577
+ The new `.update()` and enhanced `getNestedLocator()` methods leverage TypeScript's type system to provide accurate Intellisense suggestions, enhancing developer experience and reducing errors.
578
+
579
+ ## Conclusion
580
+
581
+ These enhancements to `LocatorSchema` and its associated methods significantly improve the flexibility, readability, and maintainability of locator management within your POMs. By adopting the new `filter` property, `.addFilter()` method, and updated `.update()` & `getNestedLocator()` methods, you can construct more precise and adaptable locators, facilitating robust and scalable test automation.
582
+
583
+ **Note:**
584
+ Ensure to migrate away from the deprecated `.update`, `.updates`, and old `getNestedLocator` methods before upgrading to version 2.0.0 to maintain compatibility and leverage the full benefits of these enhancements.
585
+
3
586
  ## 1.1.1
4
587
 
5
588
  ### Patch Changes