expect-sdk 0.0.24
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/.claude/settings.local.json +48 -0
- package/.expect/replays/08a6cacb-cdfb-47ba-9cf7-41a13ac07a36.html +286 -0
- package/.expect/replays/08a6cacb-cdfb-47ba-9cf7-41a13ac07a36.ndjson +2 -0
- package/.expect/replays/08a6cacb-cdfb-47ba-9cf7-41a13ac07a36.ndjson.js +2 -0
- package/.expect/replays/0abc2d0c-30d4-405b-8f6a-ad55eab10797.html +286 -0
- package/.expect/replays/0abc2d0c-30d4-405b-8f6a-ad55eab10797.ndjson +2 -0
- package/.expect/replays/0abc2d0c-30d4-405b-8f6a-ad55eab10797.ndjson.js +2 -0
- package/.expect/replays/0be009ad-fc3d-4aa8-81e2-2473dddf876a.html +286 -0
- package/.expect/replays/0be009ad-fc3d-4aa8-81e2-2473dddf876a.ndjson +7 -0
- package/.expect/replays/0be009ad-fc3d-4aa8-81e2-2473dddf876a.ndjson.js +2 -0
- package/.expect/replays/0bf0f4f1-9c7d-4dac-ae8f-6990ec08b82b.html +286 -0
- package/.expect/replays/0bf0f4f1-9c7d-4dac-ae8f-6990ec08b82b.ndjson +10 -0
- package/.expect/replays/0bf0f4f1-9c7d-4dac-ae8f-6990ec08b82b.ndjson.js +2 -0
- package/.expect/replays/0cdb0236-3fdf-4c48-8ef1-fbc8539a8d32.html +286 -0
- package/.expect/replays/0cdb0236-3fdf-4c48-8ef1-fbc8539a8d32.ndjson +49 -0
- package/.expect/replays/0cdb0236-3fdf-4c48-8ef1-fbc8539a8d32.ndjson.js +2 -0
- package/.expect/replays/0dbbf37b-6749-4973-ae20-b37b9b734ce6.html +286 -0
- package/.expect/replays/0dbbf37b-6749-4973-ae20-b37b9b734ce6.ndjson +44 -0
- package/.expect/replays/0dbbf37b-6749-4973-ae20-b37b9b734ce6.ndjson.js +2 -0
- package/.expect/replays/15009a95-16f4-4f14-9f52-a2a650b6de23.html +286 -0
- package/.expect/replays/15009a95-16f4-4f14-9f52-a2a650b6de23.ndjson +23 -0
- package/.expect/replays/15009a95-16f4-4f14-9f52-a2a650b6de23.ndjson.js +2 -0
- package/.expect/replays/2095af47-e1dc-444b-ab84-f614d755fd04.html +286 -0
- package/.expect/replays/2095af47-e1dc-444b-ab84-f614d755fd04.ndjson +10 -0
- package/.expect/replays/2095af47-e1dc-444b-ab84-f614d755fd04.ndjson.js +2 -0
- package/.expect/replays/26962ef6-a3ac-4033-aee6-d09414c2232d.html +286 -0
- package/.expect/replays/26962ef6-a3ac-4033-aee6-d09414c2232d.ndjson +8 -0
- package/.expect/replays/26962ef6-a3ac-4033-aee6-d09414c2232d.ndjson.js +2 -0
- package/.expect/replays/28d1ed69-05c0-44dc-a369-77b0d37b64c8.html +286 -0
- package/.expect/replays/28d1ed69-05c0-44dc-a369-77b0d37b64c8.ndjson +12 -0
- package/.expect/replays/28d1ed69-05c0-44dc-a369-77b0d37b64c8.ndjson.js +2 -0
- package/.expect/replays/2ece9734-85bf-4d45-9738-7e3c7a4b6c9e.html +286 -0
- package/.expect/replays/2ece9734-85bf-4d45-9738-7e3c7a4b6c9e.ndjson +20 -0
- package/.expect/replays/2ece9734-85bf-4d45-9738-7e3c7a4b6c9e.ndjson.js +2 -0
- package/.expect/replays/301ecd28-499b-4367-80c8-8f5c740a4011.html +286 -0
- package/.expect/replays/301ecd28-499b-4367-80c8-8f5c740a4011.ndjson +6 -0
- package/.expect/replays/301ecd28-499b-4367-80c8-8f5c740a4011.ndjson.js +2 -0
- package/.expect/replays/323fdb67-102e-45c2-a6ae-2ed78e590928.html +286 -0
- package/.expect/replays/323fdb67-102e-45c2-a6ae-2ed78e590928.ndjson +7 -0
- package/.expect/replays/323fdb67-102e-45c2-a6ae-2ed78e590928.ndjson.js +2 -0
- package/.expect/replays/338b9a53-a211-448a-99cd-a95873edef79.html +286 -0
- package/.expect/replays/338b9a53-a211-448a-99cd-a95873edef79.ndjson +7 -0
- package/.expect/replays/338b9a53-a211-448a-99cd-a95873edef79.ndjson.js +2 -0
- package/.expect/replays/33bfd732-9116-4200-b732-41faba42ae75.html +286 -0
- package/.expect/replays/33bfd732-9116-4200-b732-41faba42ae75.ndjson +10 -0
- package/.expect/replays/33bfd732-9116-4200-b732-41faba42ae75.ndjson.js +2 -0
- package/.expect/replays/33eb7162-e0c8-40ae-8e72-8ad273b0fca7.html +286 -0
- package/.expect/replays/33eb7162-e0c8-40ae-8e72-8ad273b0fca7.ndjson +7 -0
- package/.expect/replays/33eb7162-e0c8-40ae-8e72-8ad273b0fca7.ndjson.js +2 -0
- package/.expect/replays/365939fb-9693-4219-b5f5-0bafef524617.html +286 -0
- package/.expect/replays/365939fb-9693-4219-b5f5-0bafef524617.ndjson +2 -0
- package/.expect/replays/365939fb-9693-4219-b5f5-0bafef524617.ndjson.js +2 -0
- package/.expect/replays/383ea7a8-0ff8-4958-a148-3032f0a52eab.html +286 -0
- package/.expect/replays/383ea7a8-0ff8-4958-a148-3032f0a52eab.ndjson +54 -0
- package/.expect/replays/383ea7a8-0ff8-4958-a148-3032f0a52eab.ndjson.js +2 -0
- package/.expect/replays/397a5665-4344-441b-a424-2ac53df712a1.html +286 -0
- package/.expect/replays/397a5665-4344-441b-a424-2ac53df712a1.ndjson +14 -0
- package/.expect/replays/397a5665-4344-441b-a424-2ac53df712a1.ndjson.js +2 -0
- package/.expect/replays/39a5d9cc-72d0-483d-bfcc-506a6fc85f13.html +286 -0
- package/.expect/replays/39a5d9cc-72d0-483d-bfcc-506a6fc85f13.ndjson +10 -0
- package/.expect/replays/39a5d9cc-72d0-483d-bfcc-506a6fc85f13.ndjson.js +2 -0
- package/.expect/replays/39ccd62e-2f4c-4a4a-9b43-40524b7a945b.html +286 -0
- package/.expect/replays/39ccd62e-2f4c-4a4a-9b43-40524b7a945b.ndjson +14 -0
- package/.expect/replays/39ccd62e-2f4c-4a4a-9b43-40524b7a945b.ndjson.js +2 -0
- package/.expect/replays/3eff073f-7e14-49d8-a2e2-1b7634a117cd.html +286 -0
- package/.expect/replays/3eff073f-7e14-49d8-a2e2-1b7634a117cd.ndjson +45 -0
- package/.expect/replays/3eff073f-7e14-49d8-a2e2-1b7634a117cd.ndjson.js +2 -0
- package/.expect/replays/3f144b2f-78ce-4d23-bd96-99994bf07edd.html +286 -0
- package/.expect/replays/3f144b2f-78ce-4d23-bd96-99994bf07edd.ndjson +34 -0
- package/.expect/replays/3f144b2f-78ce-4d23-bd96-99994bf07edd.ndjson.js +2 -0
- package/.expect/replays/436e0182-8590-4520-97b5-d62b08ebe822.html +286 -0
- package/.expect/replays/436e0182-8590-4520-97b5-d62b08ebe822.ndjson +15 -0
- package/.expect/replays/436e0182-8590-4520-97b5-d62b08ebe822.ndjson.js +2 -0
- package/.expect/replays/43e9a6e6-ce7d-4ed4-b593-f07ce6d53009.html +286 -0
- package/.expect/replays/43e9a6e6-ce7d-4ed4-b593-f07ce6d53009.ndjson +85 -0
- package/.expect/replays/43e9a6e6-ce7d-4ed4-b593-f07ce6d53009.ndjson.js +2 -0
- package/.expect/replays/4568fc07-ea40-4740-be6f-0f6a8e632175.html +286 -0
- package/.expect/replays/4568fc07-ea40-4740-be6f-0f6a8e632175.ndjson +53 -0
- package/.expect/replays/4568fc07-ea40-4740-be6f-0f6a8e632175.ndjson.js +2 -0
- package/.expect/replays/47b43574-9b41-44be-af9c-fb7c0a81196f.html +286 -0
- package/.expect/replays/47b43574-9b41-44be-af9c-fb7c0a81196f.ndjson +2 -0
- package/.expect/replays/47b43574-9b41-44be-af9c-fb7c0a81196f.ndjson.js +2 -0
- package/.expect/replays/47d846d6-f86e-46e4-94d7-f0abff232b20.html +286 -0
- package/.expect/replays/47d846d6-f86e-46e4-94d7-f0abff232b20.ndjson +10 -0
- package/.expect/replays/47d846d6-f86e-46e4-94d7-f0abff232b20.ndjson.js +2 -0
- package/.expect/replays/486cd227-9d22-49cf-b050-cb546d374206.html +286 -0
- package/.expect/replays/486cd227-9d22-49cf-b050-cb546d374206.ndjson +2 -0
- package/.expect/replays/486cd227-9d22-49cf-b050-cb546d374206.ndjson.js +2 -0
- package/.expect/replays/4c53e4c2-ece4-4767-87fe-394fe0ab4300.html +286 -0
- package/.expect/replays/4c53e4c2-ece4-4767-87fe-394fe0ab4300.ndjson +33 -0
- package/.expect/replays/4c53e4c2-ece4-4767-87fe-394fe0ab4300.ndjson.js +2 -0
- package/.expect/replays/4d1c0166-77e7-49ee-9bdc-9e3382e7f60c.html +286 -0
- package/.expect/replays/4d1c0166-77e7-49ee-9bdc-9e3382e7f60c.ndjson +58 -0
- package/.expect/replays/4d1c0166-77e7-49ee-9bdc-9e3382e7f60c.ndjson.js +2 -0
- package/.expect/replays/4ee7ecf6-20ce-42c3-8ed6-9476e74498eb.html +286 -0
- package/.expect/replays/4ee7ecf6-20ce-42c3-8ed6-9476e74498eb.ndjson +10 -0
- package/.expect/replays/4ee7ecf6-20ce-42c3-8ed6-9476e74498eb.ndjson.js +2 -0
- package/.expect/replays/5438a8a6-ef42-42e2-8764-fde7c6529d95.html +286 -0
- package/.expect/replays/5438a8a6-ef42-42e2-8764-fde7c6529d95.ndjson +33 -0
- package/.expect/replays/5438a8a6-ef42-42e2-8764-fde7c6529d95.ndjson.js +2 -0
- package/.expect/replays/5709193a-1153-46b2-b19c-736e4bda525d.html +286 -0
- package/.expect/replays/5709193a-1153-46b2-b19c-736e4bda525d.ndjson +16 -0
- package/.expect/replays/5709193a-1153-46b2-b19c-736e4bda525d.ndjson.js +2 -0
- package/.expect/replays/5eee229c-5e60-4cb2-a1c2-f7720a94ea1c.html +286 -0
- package/.expect/replays/5eee229c-5e60-4cb2-a1c2-f7720a94ea1c.ndjson +2 -0
- package/.expect/replays/5eee229c-5e60-4cb2-a1c2-f7720a94ea1c.ndjson.js +2 -0
- package/.expect/replays/63657ec6-87d8-4250-a0db-05d12fa2983e.html +286 -0
- package/.expect/replays/63657ec6-87d8-4250-a0db-05d12fa2983e.ndjson +48 -0
- package/.expect/replays/63657ec6-87d8-4250-a0db-05d12fa2983e.ndjson.js +2 -0
- package/.expect/replays/6623c0d4-cfcf-461a-8029-0f157ff21080.html +286 -0
- package/.expect/replays/6623c0d4-cfcf-461a-8029-0f157ff21080.ndjson +8 -0
- package/.expect/replays/6623c0d4-cfcf-461a-8029-0f157ff21080.ndjson.js +2 -0
- package/.expect/replays/69a4dab6-06ad-4d54-99c9-1113d6f5a033.html +286 -0
- package/.expect/replays/69a4dab6-06ad-4d54-99c9-1113d6f5a033.ndjson +7 -0
- package/.expect/replays/69a4dab6-06ad-4d54-99c9-1113d6f5a033.ndjson.js +2 -0
- package/.expect/replays/6b3fae01-5e61-48e3-b675-334572bdaf67.html +286 -0
- package/.expect/replays/6b3fae01-5e61-48e3-b675-334572bdaf67.ndjson +14 -0
- package/.expect/replays/6b3fae01-5e61-48e3-b675-334572bdaf67.ndjson.js +2 -0
- package/.expect/replays/709859dd-cd9d-4f4a-93f3-0185631feaf5.html +286 -0
- package/.expect/replays/709859dd-cd9d-4f4a-93f3-0185631feaf5.ndjson +2 -0
- package/.expect/replays/709859dd-cd9d-4f4a-93f3-0185631feaf5.ndjson.js +2 -0
- package/.expect/replays/76b454d4-ba48-47a0-9336-486a7b106322.html +286 -0
- package/.expect/replays/76b454d4-ba48-47a0-9336-486a7b106322.ndjson +37 -0
- package/.expect/replays/76b454d4-ba48-47a0-9336-486a7b106322.ndjson.js +2 -0
- package/.expect/replays/76c75bfa-d266-487e-a4d2-54d6f64760b5.html +286 -0
- package/.expect/replays/76c75bfa-d266-487e-a4d2-54d6f64760b5.ndjson +18 -0
- package/.expect/replays/76c75bfa-d266-487e-a4d2-54d6f64760b5.ndjson.js +2 -0
- package/.expect/replays/78333bb7-5172-4839-98a0-745372a1032b.html +286 -0
- package/.expect/replays/78333bb7-5172-4839-98a0-745372a1032b.ndjson +65 -0
- package/.expect/replays/78333bb7-5172-4839-98a0-745372a1032b.ndjson.js +2 -0
- package/.expect/replays/7be24ce2-8544-492e-9b66-b56c0c1e027b.html +286 -0
- package/.expect/replays/7be24ce2-8544-492e-9b66-b56c0c1e027b.ndjson +10 -0
- package/.expect/replays/7be24ce2-8544-492e-9b66-b56c0c1e027b.ndjson.js +2 -0
- package/.expect/replays/825423b3-7e83-4037-b0b7-c4eafe6282b8.html +286 -0
- package/.expect/replays/825423b3-7e83-4037-b0b7-c4eafe6282b8.ndjson +54 -0
- package/.expect/replays/825423b3-7e83-4037-b0b7-c4eafe6282b8.ndjson.js +2 -0
- package/.expect/replays/83641ba6-ccfa-4400-88ee-b8e4a1775c12.html +286 -0
- package/.expect/replays/83641ba6-ccfa-4400-88ee-b8e4a1775c12.ndjson +18 -0
- package/.expect/replays/83641ba6-ccfa-4400-88ee-b8e4a1775c12.ndjson.js +2 -0
- package/.expect/replays/87af45a5-dd8f-433a-8e5d-5ab136d653b9.html +286 -0
- package/.expect/replays/87af45a5-dd8f-433a-8e5d-5ab136d653b9.ndjson +3 -0
- package/.expect/replays/87af45a5-dd8f-433a-8e5d-5ab136d653b9.ndjson.js +2 -0
- package/.expect/replays/882a48e3-15b5-47fb-9f96-3e63c282557c.html +286 -0
- package/.expect/replays/882a48e3-15b5-47fb-9f96-3e63c282557c.ndjson +44 -0
- package/.expect/replays/882a48e3-15b5-47fb-9f96-3e63c282557c.ndjson.js +2 -0
- package/.expect/replays/8efca11f-3649-4433-a61c-f3b844aaa0b9.html +286 -0
- package/.expect/replays/8efca11f-3649-4433-a61c-f3b844aaa0b9.ndjson +33 -0
- package/.expect/replays/8efca11f-3649-4433-a61c-f3b844aaa0b9.ndjson.js +2 -0
- package/.expect/replays/8f563116-c926-4de3-9d16-3dfe33dd52b6.html +286 -0
- package/.expect/replays/8f563116-c926-4de3-9d16-3dfe33dd52b6.ndjson +12 -0
- package/.expect/replays/8f563116-c926-4de3-9d16-3dfe33dd52b6.ndjson.js +2 -0
- package/.expect/replays/9321eb59-9587-4819-80b0-b387c50aaaf4.html +286 -0
- package/.expect/replays/9321eb59-9587-4819-80b0-b387c50aaaf4.ndjson +10 -0
- package/.expect/replays/9321eb59-9587-4819-80b0-b387c50aaaf4.ndjson.js +2 -0
- package/.expect/replays/94cb0431-e2a6-4a8d-800a-6918adb25660.html +286 -0
- package/.expect/replays/94cb0431-e2a6-4a8d-800a-6918adb25660.ndjson +10 -0
- package/.expect/replays/94cb0431-e2a6-4a8d-800a-6918adb25660.ndjson.js +2 -0
- package/.expect/replays/94d30b76-3fb0-476a-93c5-c9acb88eb45a.html +286 -0
- package/.expect/replays/94d30b76-3fb0-476a-93c5-c9acb88eb45a.ndjson +43 -0
- package/.expect/replays/94d30b76-3fb0-476a-93c5-c9acb88eb45a.ndjson.js +2 -0
- package/.expect/replays/94da15c7-217f-4167-aad9-0ed7d69fb1a9.html +286 -0
- package/.expect/replays/94da15c7-217f-4167-aad9-0ed7d69fb1a9.ndjson +2 -0
- package/.expect/replays/94da15c7-217f-4167-aad9-0ed7d69fb1a9.ndjson.js +2 -0
- package/.expect/replays/9998db1c-b008-497f-8a13-1fc0eb6a8845.html +286 -0
- package/.expect/replays/9998db1c-b008-497f-8a13-1fc0eb6a8845.ndjson +31 -0
- package/.expect/replays/9998db1c-b008-497f-8a13-1fc0eb6a8845.ndjson.js +2 -0
- package/.expect/replays/9c54b7fe-d113-4d99-9df9-42af0779a176.html +286 -0
- package/.expect/replays/9c54b7fe-d113-4d99-9df9-42af0779a176.ndjson +52 -0
- package/.expect/replays/9c54b7fe-d113-4d99-9df9-42af0779a176.ndjson.js +2 -0
- package/.expect/replays/9d8b9d7f-2fad-44c8-92bc-5377adb4ca1b.html +286 -0
- package/.expect/replays/9d8b9d7f-2fad-44c8-92bc-5377adb4ca1b.ndjson +24 -0
- package/.expect/replays/9d8b9d7f-2fad-44c8-92bc-5377adb4ca1b.ndjson.js +2 -0
- package/.expect/replays/9f6d215a-17bd-44f8-be75-4f21c36cb7a5.html +286 -0
- package/.expect/replays/9f6d215a-17bd-44f8-be75-4f21c36cb7a5.ndjson +14 -0
- package/.expect/replays/9f6d215a-17bd-44f8-be75-4f21c36cb7a5.ndjson.js +2 -0
- package/.expect/replays/9fddc5ad-02e8-4f8e-96cf-c249444bf123.html +286 -0
- package/.expect/replays/9fddc5ad-02e8-4f8e-96cf-c249444bf123.ndjson +2 -0
- package/.expect/replays/9fddc5ad-02e8-4f8e-96cf-c249444bf123.ndjson.js +2 -0
- package/.expect/replays/a1e5b091-888f-43c5-95c3-c25f91f8925a.html +286 -0
- package/.expect/replays/a1e5b091-888f-43c5-95c3-c25f91f8925a.ndjson +12 -0
- package/.expect/replays/a1e5b091-888f-43c5-95c3-c25f91f8925a.ndjson.js +2 -0
- package/.expect/replays/a2e5372c-e459-4c47-8133-921303e7c74d.html +286 -0
- package/.expect/replays/a2e5372c-e459-4c47-8133-921303e7c74d.ndjson +2 -0
- package/.expect/replays/a2e5372c-e459-4c47-8133-921303e7c74d.ndjson.js +2 -0
- package/.expect/replays/a41a8fe3-4abc-4bde-bea0-526ef9d5c16f.html +286 -0
- package/.expect/replays/a41a8fe3-4abc-4bde-bea0-526ef9d5c16f.ndjson +16 -0
- package/.expect/replays/a41a8fe3-4abc-4bde-bea0-526ef9d5c16f.ndjson.js +2 -0
- package/.expect/replays/a726e14d-080c-4b30-b413-fcf6d14d0daf.html +286 -0
- package/.expect/replays/a726e14d-080c-4b30-b413-fcf6d14d0daf.ndjson +8 -0
- package/.expect/replays/a726e14d-080c-4b30-b413-fcf6d14d0daf.ndjson.js +2 -0
- package/.expect/replays/ab5651bb-3528-4b4b-809d-1e6d1fef2ade.html +286 -0
- package/.expect/replays/ab5651bb-3528-4b4b-809d-1e6d1fef2ade.ndjson +52 -0
- package/.expect/replays/ab5651bb-3528-4b4b-809d-1e6d1fef2ade.ndjson.js +2 -0
- package/.expect/replays/ac83d5ac-73c5-48f0-8d73-53f50151e4bf.html +286 -0
- package/.expect/replays/ac83d5ac-73c5-48f0-8d73-53f50151e4bf.ndjson +2 -0
- package/.expect/replays/ac83d5ac-73c5-48f0-8d73-53f50151e4bf.ndjson.js +2 -0
- package/.expect/replays/afeb4664-90ba-4525-9f4e-54349c5254f4.html +286 -0
- package/.expect/replays/afeb4664-90ba-4525-9f4e-54349c5254f4.ndjson +8 -0
- package/.expect/replays/afeb4664-90ba-4525-9f4e-54349c5254f4.ndjson.js +2 -0
- package/.expect/replays/b205a14f-ea04-41f7-b3f6-6a24881a4907.html +286 -0
- package/.expect/replays/b205a14f-ea04-41f7-b3f6-6a24881a4907.ndjson +44 -0
- package/.expect/replays/b205a14f-ea04-41f7-b3f6-6a24881a4907.ndjson.js +2 -0
- package/.expect/replays/b23eafd9-c876-4a66-a5e6-8d560672d5a8.html +286 -0
- package/.expect/replays/b23eafd9-c876-4a66-a5e6-8d560672d5a8.ndjson +84 -0
- package/.expect/replays/b23eafd9-c876-4a66-a5e6-8d560672d5a8.ndjson.js +2 -0
- package/.expect/replays/b4116451-32d0-4b85-a404-503bb4123815.html +286 -0
- package/.expect/replays/b4116451-32d0-4b85-a404-503bb4123815.ndjson +10 -0
- package/.expect/replays/b4116451-32d0-4b85-a404-503bb4123815.ndjson.js +2 -0
- package/.expect/replays/b649a6ef-45e3-49c2-bd02-284deb7ea9b1.html +286 -0
- package/.expect/replays/b649a6ef-45e3-49c2-bd02-284deb7ea9b1.ndjson +32 -0
- package/.expect/replays/b649a6ef-45e3-49c2-bd02-284deb7ea9b1.ndjson.js +2 -0
- package/.expect/replays/b8487d6d-87db-4453-9a87-f3ef57b9596a.html +286 -0
- package/.expect/replays/b8487d6d-87db-4453-9a87-f3ef57b9596a.ndjson +12 -0
- package/.expect/replays/b8487d6d-87db-4453-9a87-f3ef57b9596a.ndjson.js +2 -0
- package/.expect/replays/b9126d9e-93e9-44d1-901b-f4e4700a2ac7.html +286 -0
- package/.expect/replays/b9126d9e-93e9-44d1-901b-f4e4700a2ac7.ndjson +16 -0
- package/.expect/replays/b9126d9e-93e9-44d1-901b-f4e4700a2ac7.ndjson.js +2 -0
- package/.expect/replays/bc9ac26e-6915-466d-a250-61679c7d8785.html +286 -0
- package/.expect/replays/bc9ac26e-6915-466d-a250-61679c7d8785.ndjson +58 -0
- package/.expect/replays/bc9ac26e-6915-466d-a250-61679c7d8785.ndjson.js +2 -0
- package/.expect/replays/bf71f292-d0fa-4b38-88d2-838d631b4fd3.html +286 -0
- package/.expect/replays/bf71f292-d0fa-4b38-88d2-838d631b4fd3.ndjson +18 -0
- package/.expect/replays/bf71f292-d0fa-4b38-88d2-838d631b4fd3.ndjson.js +2 -0
- package/.expect/replays/c064a436-aa74-411c-b4ff-228e6016748c.html +286 -0
- package/.expect/replays/c064a436-aa74-411c-b4ff-228e6016748c.ndjson +14 -0
- package/.expect/replays/c064a436-aa74-411c-b4ff-228e6016748c.ndjson.js +2 -0
- package/.expect/replays/c293319a-56fc-47c2-be0a-efebab7e5547.html +286 -0
- package/.expect/replays/c293319a-56fc-47c2-be0a-efebab7e5547.ndjson +10 -0
- package/.expect/replays/c293319a-56fc-47c2-be0a-efebab7e5547.ndjson.js +2 -0
- package/.expect/replays/c48e7ccb-f40a-4d86-9ada-f9ac0f174809.html +286 -0
- package/.expect/replays/c48e7ccb-f40a-4d86-9ada-f9ac0f174809.ndjson +18 -0
- package/.expect/replays/c48e7ccb-f40a-4d86-9ada-f9ac0f174809.ndjson.js +2 -0
- package/.expect/replays/c4ec751f-4c2f-4ac4-83e1-9d53ebd275b0.html +286 -0
- package/.expect/replays/c4ec751f-4c2f-4ac4-83e1-9d53ebd275b0.ndjson +12 -0
- package/.expect/replays/c4ec751f-4c2f-4ac4-83e1-9d53ebd275b0.ndjson.js +2 -0
- package/.expect/replays/c5f8f2de-415c-4605-90e9-8a31286c1d33.html +286 -0
- package/.expect/replays/c5f8f2de-415c-4605-90e9-8a31286c1d33.ndjson +36 -0
- package/.expect/replays/c5f8f2de-415c-4605-90e9-8a31286c1d33.ndjson.js +2 -0
- package/.expect/replays/c92defec-c6f7-4334-89f4-d3b1010a592b.html +286 -0
- package/.expect/replays/c92defec-c6f7-4334-89f4-d3b1010a592b.ndjson +97 -0
- package/.expect/replays/c92defec-c6f7-4334-89f4-d3b1010a592b.ndjson.js +2 -0
- package/.expect/replays/ce024389-749b-4d9f-9cbe-d2add9b5d506.html +286 -0
- package/.expect/replays/ce024389-749b-4d9f-9cbe-d2add9b5d506.ndjson +8 -0
- package/.expect/replays/ce024389-749b-4d9f-9cbe-d2add9b5d506.ndjson.js +2 -0
- package/.expect/replays/d0106187-b170-4208-b6e2-1652906fb952.html +286 -0
- package/.expect/replays/d0106187-b170-4208-b6e2-1652906fb952.ndjson +52 -0
- package/.expect/replays/d0106187-b170-4208-b6e2-1652906fb952.ndjson.js +2 -0
- package/.expect/replays/d4aa55a2-9b5f-4eb5-b795-5324e5c5d7db.html +286 -0
- package/.expect/replays/d4aa55a2-9b5f-4eb5-b795-5324e5c5d7db.ndjson +84 -0
- package/.expect/replays/d4aa55a2-9b5f-4eb5-b795-5324e5c5d7db.ndjson.js +2 -0
- package/.expect/replays/d4fc939c-c97d-4125-95dc-a8745709e879.html +286 -0
- package/.expect/replays/d4fc939c-c97d-4125-95dc-a8745709e879.ndjson +2 -0
- package/.expect/replays/d4fc939c-c97d-4125-95dc-a8745709e879.ndjson.js +2 -0
- package/.expect/replays/d59474f3-6eee-4145-9252-ac56d7d634d1.html +286 -0
- package/.expect/replays/d59474f3-6eee-4145-9252-ac56d7d634d1.ndjson +46 -0
- package/.expect/replays/d59474f3-6eee-4145-9252-ac56d7d634d1.ndjson.js +2 -0
- package/.expect/replays/d842aee0-a2e0-45a4-84bb-02571ea704ef.html +286 -0
- package/.expect/replays/d842aee0-a2e0-45a4-84bb-02571ea704ef.ndjson +10 -0
- package/.expect/replays/d842aee0-a2e0-45a4-84bb-02571ea704ef.ndjson.js +2 -0
- package/.expect/replays/dbacab2e-6e47-4c2e-861b-881a3439cc61.html +286 -0
- package/.expect/replays/dbacab2e-6e47-4c2e-861b-881a3439cc61.ndjson +40 -0
- package/.expect/replays/dbacab2e-6e47-4c2e-861b-881a3439cc61.ndjson.js +2 -0
- package/.expect/replays/e47ad94c-1326-4e5f-9e36-9187b2475dc4.html +286 -0
- package/.expect/replays/e47ad94c-1326-4e5f-9e36-9187b2475dc4.ndjson +10 -0
- package/.expect/replays/e47ad94c-1326-4e5f-9e36-9187b2475dc4.ndjson.js +2 -0
- package/.expect/replays/e4e7c05e-0def-4eb4-80de-5c0abc6e9707.html +286 -0
- package/.expect/replays/e4e7c05e-0def-4eb4-80de-5c0abc6e9707.ndjson +32 -0
- package/.expect/replays/e4e7c05e-0def-4eb4-80de-5c0abc6e9707.ndjson.js +2 -0
- package/.expect/replays/e7fbb2f5-f5be-430f-b9d8-e9b5980c4e25.html +286 -0
- package/.expect/replays/e7fbb2f5-f5be-430f-b9d8-e9b5980c4e25.ndjson +30 -0
- package/.expect/replays/e7fbb2f5-f5be-430f-b9d8-e9b5980c4e25.ndjson.js +2 -0
- package/.expect/replays/ef25484d-6e02-4cde-bf19-7251a9ada2b3.html +286 -0
- package/.expect/replays/ef25484d-6e02-4cde-bf19-7251a9ada2b3.ndjson +37 -0
- package/.expect/replays/ef25484d-6e02-4cde-bf19-7251a9ada2b3.ndjson.js +2 -0
- package/.expect/replays/f114ddaa-f891-4be1-b626-eaeeba692e0f.html +286 -0
- package/.expect/replays/f114ddaa-f891-4be1-b626-eaeeba692e0f.ndjson +12 -0
- package/.expect/replays/f114ddaa-f891-4be1-b626-eaeeba692e0f.ndjson.js +2 -0
- package/.expect/replays/f39c87c6-34c7-4b81-8350-a644e309430f.html +286 -0
- package/.expect/replays/f39c87c6-34c7-4b81-8350-a644e309430f.ndjson +8 -0
- package/.expect/replays/f39c87c6-34c7-4b81-8350-a644e309430f.ndjson.js +2 -0
- package/.expect/replays/f6484a4f-c647-4f78-9a2e-f41f00683c15.html +286 -0
- package/.expect/replays/f6484a4f-c647-4f78-9a2e-f41f00683c15.ndjson +14 -0
- package/.expect/replays/f6484a4f-c647-4f78-9a2e-f41f00683c15.ndjson.js +2 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-check.log +6 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +12 -0
- package/LICENSE +110 -0
- package/README.md +536 -0
- package/dist/constants-DFJAD4-F.mjs +152 -0
- package/dist/constants-DFJAD4-F.mjs.map +1 -0
- package/dist/effect.d.mts +30 -0
- package/dist/effect.d.mts.map +1 -0
- package/dist/effect.mjs +2 -0
- package/dist/index.d.mts +20 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +279 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-0J0EwTM4.d.mts +124 -0
- package/dist/types-0J0EwTM4.d.mts.map +1 -0
- package/package.json +46 -0
- package/src/build-instruction.ts +35 -0
- package/src/config.ts +15 -0
- package/src/constants.ts +2 -0
- package/src/effect.ts +14 -0
- package/src/errors.ts +17 -0
- package/src/expect.ts +432 -0
- package/src/index.ts +23 -0
- package/src/layers.ts +14 -0
- package/src/result-builder.ts +184 -0
- package/src/test-run.ts +19 -0
- package/src/tool.ts +26 -0
- package/src/types.ts +98 -0
- package/tests/build-instruction.test.ts +62 -0
- package/tests/config.test.ts +44 -0
- package/tests/e2e.ts +81 -0
- package/tests/examples.test.ts +44 -0
- package/tests/expect.test.ts +143 -0
- package/tests/fixtures/fixture-server.ts +111 -0
- package/tests/result-builder.test.ts +292 -0
- package/tests/test-run.test.ts +95 -0
- package/tests/tool.test.ts +60 -0
- package/tsconfig.json +9 -0
- package/vite.config.ts +13 -0
package/src/constants.ts
ADDED
package/src/effect.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { ExpectConfigError, ExpectTimeoutError } from "./errors";
|
|
2
|
+
export { resolveUrl, buildInstruction } from "./build-instruction";
|
|
3
|
+
export { layerSdk } from "./layers";
|
|
4
|
+
export { DEFAULT_TIMEOUT_MS, DEFAULT_AGENT_BACKEND } from "./constants";
|
|
5
|
+
export { buildTestResult, buildStepResult, diffEvents, extractArtifacts } from "./result-builder";
|
|
6
|
+
export type {
|
|
7
|
+
Action,
|
|
8
|
+
BrowserName,
|
|
9
|
+
CookieInput,
|
|
10
|
+
Cookie,
|
|
11
|
+
StepResult,
|
|
12
|
+
TestResult,
|
|
13
|
+
TestEvent,
|
|
14
|
+
} from "./types";
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
export class ExpectTimeoutError extends Schema.ErrorClass<ExpectTimeoutError>("ExpectTimeoutError")(
|
|
4
|
+
{
|
|
5
|
+
_tag: Schema.tag("ExpectTimeoutError"),
|
|
6
|
+
timeoutMs: Schema.Number,
|
|
7
|
+
},
|
|
8
|
+
) {
|
|
9
|
+
message = `expect execution timed out after ${this.timeoutMs}ms`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ExpectConfigError extends Error {
|
|
13
|
+
constructor(message: string, fix: string) {
|
|
14
|
+
super(`${message}\n\nFix: ${fix}`);
|
|
15
|
+
this.name = "ExpectConfigError";
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/expect.ts
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import { Effect, Option, Stream } from "effect";
|
|
2
|
+
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
3
|
+
import { Executor, type ExecuteOptions } from "@expect/supervisor";
|
|
4
|
+
import { type ExecutedTestPlan, type ExecutionEvent, ChangesFor } from "@expect/shared/models";
|
|
5
|
+
import {
|
|
6
|
+
Cookies as CookiesService,
|
|
7
|
+
Browsers,
|
|
8
|
+
layerLive as cookiesLayerLive,
|
|
9
|
+
} from "@expect/cookies";
|
|
10
|
+
import { ExpectConfigError, ExpectTimeoutError } from "./errors";
|
|
11
|
+
import { resolveUrl, buildInstruction } from "./build-instruction";
|
|
12
|
+
import { getGlobalConfig } from "./config";
|
|
13
|
+
import { layerSdk } from "./layers";
|
|
14
|
+
import { createTestRun } from "./test-run";
|
|
15
|
+
import { buildTestResult, diffEvents, extractArtifacts } from "./result-builder";
|
|
16
|
+
import { DEFAULT_TIMEOUT_MS, DEFAULT_AGENT_BACKEND } from "./constants";
|
|
17
|
+
import type { Page } from "playwright";
|
|
18
|
+
import type {
|
|
19
|
+
TestInput,
|
|
20
|
+
TestRun,
|
|
21
|
+
TestResult,
|
|
22
|
+
TestEvent,
|
|
23
|
+
SessionConfig,
|
|
24
|
+
SessionTestInput,
|
|
25
|
+
ExpectSession,
|
|
26
|
+
Cookie,
|
|
27
|
+
BrowserName,
|
|
28
|
+
Test,
|
|
29
|
+
} from "./types";
|
|
30
|
+
|
|
31
|
+
const normalizeTestPrompts = (tests: readonly Test[]): readonly string[] =>
|
|
32
|
+
tests.map((test) => (typeof test === "string" ? test : test.prompt));
|
|
33
|
+
|
|
34
|
+
const resolveInputUrl = (input: { url?: string; page?: Page }): string => {
|
|
35
|
+
const config = getGlobalConfig();
|
|
36
|
+
|
|
37
|
+
if (input.page) {
|
|
38
|
+
return input.page.url();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (input.url !== undefined) {
|
|
42
|
+
return resolveUrl(input.url, config.baseUrl);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (config.baseUrl) {
|
|
46
|
+
return config.baseUrl;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
throw new ExpectConfigError(
|
|
50
|
+
"No URL provided and no baseUrl configured.",
|
|
51
|
+
`Expect.test({ url: "http://localhost:3000", tests: [...] })\nOr: configure({ baseUrl: "http://localhost:3000" })`,
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const validateTests = (tests: readonly Test[]): void => {
|
|
56
|
+
if (tests.length === 0) {
|
|
57
|
+
throw new ExpectConfigError(
|
|
58
|
+
"tests array is empty.",
|
|
59
|
+
`Expect.test({ url: "...", tests: ["at least one test"] })`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const validateTestInput = (input: TestInput): void => {
|
|
65
|
+
validateTests(input.tests);
|
|
66
|
+
if (input.tools && input.tools.length > 0) {
|
|
67
|
+
throw new ExpectConfigError(
|
|
68
|
+
"Custom tools are not yet supported.",
|
|
69
|
+
`Remove the tools field for now. Tool support is coming in a future release.`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (typeof input.before === "function" && !input.page) {
|
|
73
|
+
throw new ExpectConfigError(
|
|
74
|
+
"Function before requires a page.",
|
|
75
|
+
`Pass a Playwright Page: Expect.test({ page, before: async (page) => { ... }, tests: [...] })`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (typeof input.after === "function" && !input.page) {
|
|
79
|
+
throw new ExpectConfigError(
|
|
80
|
+
"Function after requires a page.",
|
|
81
|
+
`Pass a Playwright Page: Expect.test({ page, after: async (page) => { ... }, tests: [...] })`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const validateSessionConfig = (config: SessionConfig): void => {
|
|
87
|
+
if (config.browserContext) {
|
|
88
|
+
throw new ExpectConfigError(
|
|
89
|
+
"External browserContext is not yet supported.",
|
|
90
|
+
`Remove the browserContext field. The SDK manages browser lifecycle internally.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (config.tools && config.tools.length > 0) {
|
|
94
|
+
throw new ExpectConfigError(
|
|
95
|
+
"Custom tools are not yet supported.",
|
|
96
|
+
`Remove the tools field for now. Tool support is coming in a future release.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
interface ResolvedCookies {
|
|
102
|
+
readonly browserKeys: readonly string[];
|
|
103
|
+
readonly explicitCookies: readonly Cookie[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const isBrowserNameArray = (cookies: readonly unknown[]): cookies is readonly string[] =>
|
|
107
|
+
cookies.length > 0 && typeof cookies[0] === "string";
|
|
108
|
+
|
|
109
|
+
const resolveCookies = (cookies: TestInput["cookies"]): ResolvedCookies => {
|
|
110
|
+
if (cookies === undefined) return { browserKeys: [], explicitCookies: [] };
|
|
111
|
+
if (cookies === true) return { browserKeys: ["chrome"], explicitCookies: [] };
|
|
112
|
+
if (typeof cookies === "string") return { browserKeys: [cookies], explicitCookies: [] };
|
|
113
|
+
if (Array.isArray(cookies)) {
|
|
114
|
+
if (isBrowserNameArray(cookies)) {
|
|
115
|
+
return { browserKeys: cookies, explicitCookies: [] };
|
|
116
|
+
}
|
|
117
|
+
return { browserKeys: [], explicitCookies: cookies };
|
|
118
|
+
}
|
|
119
|
+
return { browserKeys: [], explicitCookies: [] };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const buildInstructionWithActions = (
|
|
123
|
+
url: string,
|
|
124
|
+
tests: readonly Test[],
|
|
125
|
+
before: TestInput["before"],
|
|
126
|
+
beforeContext: string | undefined,
|
|
127
|
+
after: TestInput["after"],
|
|
128
|
+
): string => {
|
|
129
|
+
const prompts = normalizeTestPrompts(tests);
|
|
130
|
+
let instruction = buildInstruction(url, prompts);
|
|
131
|
+
|
|
132
|
+
if (typeof before === "string") {
|
|
133
|
+
instruction = `Before: ${before}\n\n${instruction}`;
|
|
134
|
+
}
|
|
135
|
+
if (beforeContext) {
|
|
136
|
+
instruction = `Context from before: ${beforeContext}\n\n${instruction}`;
|
|
137
|
+
}
|
|
138
|
+
if (typeof after === "string") {
|
|
139
|
+
instruction = `${instruction}\n\nAfter: ${after}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return instruction;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
interface ExecutionContext {
|
|
146
|
+
readonly url: string;
|
|
147
|
+
readonly startedAt: number;
|
|
148
|
+
readonly eventBuffer: TestEvent[];
|
|
149
|
+
readonly resolveWaiter: { current: (() => void) | undefined };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const executeTests = Effect.fn("Sdk.executeTests")(function* (
|
|
153
|
+
executeOptions: ExecuteOptions,
|
|
154
|
+
context: ExecutionContext,
|
|
155
|
+
) {
|
|
156
|
+
yield* Effect.annotateCurrentSpan({
|
|
157
|
+
url: context.url,
|
|
158
|
+
isHeadless: executeOptions.isHeadless,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const executor = yield* Executor;
|
|
162
|
+
let previousEvents: readonly ExecutionEvent[] = [];
|
|
163
|
+
|
|
164
|
+
const finalExecuted = yield* executor.execute(executeOptions).pipe(
|
|
165
|
+
Stream.tap((executed: ExecutedTestPlan) =>
|
|
166
|
+
Effect.sync(() => {
|
|
167
|
+
const newEvents = diffEvents(
|
|
168
|
+
previousEvents,
|
|
169
|
+
executed.events,
|
|
170
|
+
executed,
|
|
171
|
+
context.url,
|
|
172
|
+
context.startedAt,
|
|
173
|
+
);
|
|
174
|
+
previousEvents = executed.events;
|
|
175
|
+
for (const event of newEvents) {
|
|
176
|
+
context.eventBuffer.push(event);
|
|
177
|
+
}
|
|
178
|
+
context.resolveWaiter.current?.();
|
|
179
|
+
}),
|
|
180
|
+
),
|
|
181
|
+
Stream.runLast,
|
|
182
|
+
Effect.flatMap((option) =>
|
|
183
|
+
Option.match(option, {
|
|
184
|
+
onNone: () => Effect.fail(new ExpectTimeoutError({ timeoutMs: 0 })),
|
|
185
|
+
onSome: (executed) => Effect.succeed(executed.finalizeTextBlock().synthesizeRunFinished()),
|
|
186
|
+
}),
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
const artifacts = extractArtifacts(finalExecuted.events);
|
|
191
|
+
const result = buildTestResult(finalExecuted, context.url, context.startedAt, artifacts);
|
|
192
|
+
|
|
193
|
+
if (!context.eventBuffer.some((event) => event.type === "completed")) {
|
|
194
|
+
context.eventBuffer.push({ type: "completed", result });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
context.resolveWaiter.current?.();
|
|
198
|
+
return result;
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const extractCookies = Effect.fn("Sdk.extractCookies")(function* (keys: readonly string[]) {
|
|
202
|
+
yield* Effect.annotateCurrentSpan({ browserKeys: keys.join(",") });
|
|
203
|
+
|
|
204
|
+
const cookiesService = yield* CookiesService;
|
|
205
|
+
const browsers = yield* Browsers;
|
|
206
|
+
const allBrowsers = yield* browsers.list;
|
|
207
|
+
|
|
208
|
+
const matchingBrowsers = allBrowsers.filter((browser) =>
|
|
209
|
+
keys.some((key) => {
|
|
210
|
+
if (browser._tag === "ChromiumBrowser") return browser.key === key;
|
|
211
|
+
if (browser._tag === "FirefoxBrowser") return key === "firefox";
|
|
212
|
+
if (browser._tag === "SafariBrowser") return key === "safari";
|
|
213
|
+
return false;
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const results = yield* Effect.forEach(
|
|
218
|
+
matchingBrowsers,
|
|
219
|
+
(browser) => cookiesService.extract(browser),
|
|
220
|
+
{ concurrency: "unbounded" },
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return results.flat().map((cookie): Cookie => {
|
|
224
|
+
const formatted = cookie.playwrightFormat;
|
|
225
|
+
return { ...formatted, sameSite: formatted.sameSite ?? "Lax" };
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const createAsyncIterator = (
|
|
230
|
+
eventBuffer: TestEvent[],
|
|
231
|
+
resolveWaiter: { current: (() => void) | undefined },
|
|
232
|
+
getFinished: () => boolean,
|
|
233
|
+
getError: () => unknown,
|
|
234
|
+
): (() => AsyncIterableIterator<TestEvent>) => {
|
|
235
|
+
return () => {
|
|
236
|
+
let cursor = 0;
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
async next(): Promise<IteratorResult<TestEvent>> {
|
|
240
|
+
while (true) {
|
|
241
|
+
if (cursor < eventBuffer.length) {
|
|
242
|
+
const event = eventBuffer[cursor];
|
|
243
|
+
cursor++;
|
|
244
|
+
if (event.type === "completed") {
|
|
245
|
+
return { done: true, value: undefined };
|
|
246
|
+
}
|
|
247
|
+
return { done: false, value: event };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (getFinished()) {
|
|
251
|
+
const error = getError();
|
|
252
|
+
if (error) throw error;
|
|
253
|
+
return { done: true, value: undefined };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
await new Promise<void>((resolve) => {
|
|
257
|
+
resolveWaiter.current = resolve;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
[Symbol.asyncIterator]() {
|
|
263
|
+
return this;
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const runExecution = (
|
|
270
|
+
url: string,
|
|
271
|
+
tests: readonly Test[],
|
|
272
|
+
input: {
|
|
273
|
+
cookies?: TestInput["cookies"];
|
|
274
|
+
mode?: "headed" | "headless";
|
|
275
|
+
timeout?: number;
|
|
276
|
+
isRecording?: boolean;
|
|
277
|
+
before?: TestInput["before"];
|
|
278
|
+
after?: TestInput["after"];
|
|
279
|
+
page?: Page;
|
|
280
|
+
},
|
|
281
|
+
): { promise: Promise<TestResult>; subscribe: () => AsyncIterableIterator<TestEvent> } => {
|
|
282
|
+
const config = getGlobalConfig();
|
|
283
|
+
const timeoutMs = input.timeout ?? config.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
284
|
+
const isHeadless = (input.mode ?? config.mode ?? "headless") === "headless";
|
|
285
|
+
const resolved = resolveCookies(input.cookies ?? config.cookies);
|
|
286
|
+
const rootDir = config.rootDir ?? process.cwd();
|
|
287
|
+
|
|
288
|
+
const eventBuffer: TestEvent[] = [];
|
|
289
|
+
const resolveWaiter: { current: (() => void) | undefined } = { current: undefined };
|
|
290
|
+
let finished = false;
|
|
291
|
+
let executionError: unknown;
|
|
292
|
+
|
|
293
|
+
const startExecution = async (): Promise<TestResult> => {
|
|
294
|
+
let beforeContext: string | undefined;
|
|
295
|
+
if (typeof input.before === "function" && input.page) {
|
|
296
|
+
const beforeResult = await input.before(input.page);
|
|
297
|
+
if (typeof beforeResult === "string") {
|
|
298
|
+
beforeContext = beforeResult;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const instruction = buildInstructionWithActions(
|
|
303
|
+
url,
|
|
304
|
+
tests,
|
|
305
|
+
input.before,
|
|
306
|
+
beforeContext,
|
|
307
|
+
input.after,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const executeOptions: ExecuteOptions = {
|
|
311
|
+
changesFor: ChangesFor.makeUnsafe({ _tag: "WorkingTree" }),
|
|
312
|
+
instruction,
|
|
313
|
+
isHeadless,
|
|
314
|
+
cookieBrowserKeys: [...resolved.browserKeys],
|
|
315
|
+
baseUrl: url,
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const context: ExecutionContext = {
|
|
319
|
+
url,
|
|
320
|
+
startedAt: Date.now(),
|
|
321
|
+
eventBuffer,
|
|
322
|
+
resolveWaiter,
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
const program = executeTests(executeOptions, context).pipe(
|
|
326
|
+
Effect.timeoutOrElse({
|
|
327
|
+
duration: `${timeoutMs} millis`,
|
|
328
|
+
onTimeout: () => Effect.fail(new ExpectTimeoutError({ timeoutMs })),
|
|
329
|
+
}),
|
|
330
|
+
Effect.provide(layerSdk(DEFAULT_AGENT_BACKEND, rootDir)),
|
|
331
|
+
Effect.provide(NodeServices.layer),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const result = await Effect.runPromise(program);
|
|
335
|
+
|
|
336
|
+
if (typeof input.after === "function" && input.page) {
|
|
337
|
+
await input.after(input.page);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return result;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const promise = startExecution().then(
|
|
344
|
+
(result) => {
|
|
345
|
+
finished = true;
|
|
346
|
+
resolveWaiter.current?.();
|
|
347
|
+
return result;
|
|
348
|
+
},
|
|
349
|
+
(error) => {
|
|
350
|
+
executionError = error;
|
|
351
|
+
finished = true;
|
|
352
|
+
resolveWaiter.current?.();
|
|
353
|
+
throw error;
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
const subscribe = createAsyncIterator(
|
|
358
|
+
eventBuffer,
|
|
359
|
+
resolveWaiter,
|
|
360
|
+
() => finished,
|
|
361
|
+
() => executionError,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
return { promise, subscribe };
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const test = (input: TestInput): TestRun => {
|
|
368
|
+
validateTestInput(input);
|
|
369
|
+
const url = resolveInputUrl(input);
|
|
370
|
+
const { promise, subscribe } = runExecution(url, input.tests, input);
|
|
371
|
+
return createTestRun({ promise, subscribe });
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const session = (config: SessionConfig): ExpectSession => {
|
|
375
|
+
validateSessionConfig(config);
|
|
376
|
+
|
|
377
|
+
const sessionTest = (input: SessionTestInput): TestRun => {
|
|
378
|
+
validateTests(input.tests);
|
|
379
|
+
const testUrl = input.url ?? config.url;
|
|
380
|
+
const globalCfg = getGlobalConfig();
|
|
381
|
+
|
|
382
|
+
let resolvedUrl: string;
|
|
383
|
+
if (testUrl !== undefined) {
|
|
384
|
+
resolvedUrl = resolveUrl(testUrl, globalCfg.baseUrl ?? config.url);
|
|
385
|
+
} else if (config.url) {
|
|
386
|
+
resolvedUrl = config.url;
|
|
387
|
+
} else if (globalCfg.baseUrl) {
|
|
388
|
+
resolvedUrl = globalCfg.baseUrl;
|
|
389
|
+
} else {
|
|
390
|
+
throw new ExpectConfigError(
|
|
391
|
+
"No URL provided for session test and no baseUrl configured.",
|
|
392
|
+
`session.test({ url: "/page", tests: [...] })`,
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const { promise, subscribe } = runExecution(resolvedUrl, input.tests, {
|
|
397
|
+
cookies: config.cookies,
|
|
398
|
+
mode: input.mode ?? config.mode,
|
|
399
|
+
timeout: input.timeout ?? config.timeout,
|
|
400
|
+
isRecording: input.isRecording ?? config.isRecording,
|
|
401
|
+
before: input.before,
|
|
402
|
+
after: input.after,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return createTestRun({ promise, subscribe });
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const close = async (): Promise<void> => {
|
|
409
|
+
// HACK: browser lifecycle is managed by the executor internally for now
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
test: sessionTest,
|
|
414
|
+
close,
|
|
415
|
+
[Symbol.asyncDispose]: close,
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const cookies = (browser: true | BrowserName | BrowserName[]): Promise<Cookie[]> => {
|
|
420
|
+
const keys: string[] =
|
|
421
|
+
browser === true ? ["chrome"] : typeof browser === "string" ? [browser] : browser;
|
|
422
|
+
|
|
423
|
+
return Effect.runPromise(
|
|
424
|
+
Effect.scoped(extractCookies(keys)).pipe(
|
|
425
|
+
Effect.provide(CookiesService.layer),
|
|
426
|
+
Effect.provide(cookiesLayerLive),
|
|
427
|
+
Effect.provide(NodeServices.layer),
|
|
428
|
+
),
|
|
429
|
+
);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
export const Expect = { test, session, cookies } as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { Expect, Expect as default } from "./expect";
|
|
2
|
+
export { tool } from "./tool";
|
|
3
|
+
export { defineConfig, configure } from "./config";
|
|
4
|
+
export { ExpectConfigError } from "./errors";
|
|
5
|
+
export { DEFAULT_TIMEOUT_MS } from "./constants";
|
|
6
|
+
export type {
|
|
7
|
+
Action,
|
|
8
|
+
BrowserName,
|
|
9
|
+
Cookie,
|
|
10
|
+
CookieInput,
|
|
11
|
+
ExpectConfig,
|
|
12
|
+
ExpectSession,
|
|
13
|
+
SessionConfig,
|
|
14
|
+
SessionTestInput,
|
|
15
|
+
Status,
|
|
16
|
+
StepResult,
|
|
17
|
+
Test,
|
|
18
|
+
TestEvent,
|
|
19
|
+
TestInput,
|
|
20
|
+
TestResult,
|
|
21
|
+
TestRun,
|
|
22
|
+
Tool,
|
|
23
|
+
} from "./types";
|
package/src/layers.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Layer, References } from "effect";
|
|
2
|
+
import { Executor, Git } from "@expect/supervisor";
|
|
3
|
+
import { Agent, type AgentBackend } from "@expect/agent";
|
|
4
|
+
|
|
5
|
+
export const layerSdk = (agentBackend: AgentBackend, rootDir: string) => {
|
|
6
|
+
const gitLayer = Git.withRepoRoot(rootDir);
|
|
7
|
+
const agentLayer = Agent.layerFor(agentBackend);
|
|
8
|
+
const executorLayer = Executor.layer.pipe(Layer.provide(gitLayer));
|
|
9
|
+
|
|
10
|
+
return Layer.mergeAll(executorLayer, gitLayer).pipe(
|
|
11
|
+
Layer.provideMerge(agentLayer),
|
|
12
|
+
Layer.provideMerge(Layer.succeed(References.MinimumLogLevel, "Error")),
|
|
13
|
+
);
|
|
14
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { DateTime, Option } from "effect";
|
|
2
|
+
import type { ExecutedTestPlan, TestPlanStep, ExecutionEvent } from "@expect/shared/models";
|
|
3
|
+
import type { Status, StepResult, TestResult, TestEvent } from "./types";
|
|
4
|
+
|
|
5
|
+
const REPLAY_SESSION_PREFIX = "rrweb replay:";
|
|
6
|
+
const SCREENSHOT_PREFIX = "Screenshot:";
|
|
7
|
+
|
|
8
|
+
const stepDurationMs = (step: TestPlanStep): number => {
|
|
9
|
+
if (Option.isNone(step.startedAt)) return 0;
|
|
10
|
+
if (Option.isNone(step.endedAt))
|
|
11
|
+
return Date.now() - Number(DateTime.toEpochMillis(step.startedAt.value));
|
|
12
|
+
return (
|
|
13
|
+
Number(DateTime.toEpochMillis(step.endedAt.value)) -
|
|
14
|
+
Number(DateTime.toEpochMillis(step.startedAt.value))
|
|
15
|
+
);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const stepStatusToResultStatus = (status: string): Status => {
|
|
19
|
+
if (status === "passed") return "passed";
|
|
20
|
+
if (status === "failed" || status === "skipped") return "failed";
|
|
21
|
+
return "pending";
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export interface ExecutionArtifacts {
|
|
25
|
+
readonly recordingPath: string | undefined;
|
|
26
|
+
readonly screenshotPaths: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const extractArtifacts = (events: readonly ExecutionEvent[]): ExecutionArtifacts => {
|
|
30
|
+
const closeResult = events
|
|
31
|
+
.slice()
|
|
32
|
+
.reverse()
|
|
33
|
+
.find(
|
|
34
|
+
(event) =>
|
|
35
|
+
event._tag === "ToolResult" &&
|
|
36
|
+
event.toolName === "close" &&
|
|
37
|
+
!event.isError &&
|
|
38
|
+
event.result.length > 0,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (!closeResult || closeResult._tag !== "ToolResult") {
|
|
42
|
+
return { recordingPath: undefined, screenshotPaths: [] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const lines = closeResult.result
|
|
46
|
+
.split("\n")
|
|
47
|
+
.map((line) => line.trim())
|
|
48
|
+
.filter((line) => line.length > 0);
|
|
49
|
+
|
|
50
|
+
const replayLine = lines.find((line) => line.startsWith(REPLAY_SESSION_PREFIX));
|
|
51
|
+
const recordingPath = replayLine?.replace(REPLAY_SESSION_PREFIX, "").trim() || undefined;
|
|
52
|
+
|
|
53
|
+
const screenshotPaths = lines
|
|
54
|
+
.filter((line) => line.startsWith(SCREENSHOT_PREFIX))
|
|
55
|
+
.map((line) => line.replace(SCREENSHOT_PREFIX, "").trim())
|
|
56
|
+
.filter((value) => value.length > 0);
|
|
57
|
+
|
|
58
|
+
return { recordingPath, screenshotPaths };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const buildStepResult = (
|
|
62
|
+
step: TestPlanStep,
|
|
63
|
+
screenshotPaths: readonly string[],
|
|
64
|
+
stepIndex: number,
|
|
65
|
+
): StepResult => ({
|
|
66
|
+
title: step.title,
|
|
67
|
+
status: stepStatusToResultStatus(step.status),
|
|
68
|
+
summary: Option.getOrElse(step.summary, () => ""),
|
|
69
|
+
screenshotPath: screenshotPaths[stepIndex],
|
|
70
|
+
duration: stepDurationMs(step),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const buildTestResult = (
|
|
74
|
+
executed: ExecutedTestPlan,
|
|
75
|
+
url: string,
|
|
76
|
+
startedAt: number,
|
|
77
|
+
artifacts: ExecutionArtifacts,
|
|
78
|
+
): TestResult => {
|
|
79
|
+
const steps = executed.steps.map((step, index) =>
|
|
80
|
+
buildStepResult(step, artifacts.screenshotPaths, index),
|
|
81
|
+
);
|
|
82
|
+
const errors = steps.filter((step) => step.status === "failed");
|
|
83
|
+
const hasFailure = errors.length > 0;
|
|
84
|
+
const hasPending = steps.some((step) => step.status === "pending");
|
|
85
|
+
|
|
86
|
+
let status: Status = "passed";
|
|
87
|
+
if (hasFailure) status = "failed";
|
|
88
|
+
else if (hasPending || steps.length === 0) status = "pending";
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
status,
|
|
92
|
+
url,
|
|
93
|
+
duration: Date.now() - startedAt,
|
|
94
|
+
recordingPath: artifacts.recordingPath,
|
|
95
|
+
steps,
|
|
96
|
+
errors,
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
interface DiffContext {
|
|
101
|
+
readonly stepMap: ReadonlyMap<string, TestPlanStep>;
|
|
102
|
+
readonly stepIndexMap: ReadonlyMap<string, number>;
|
|
103
|
+
readonly artifacts: ExecutionArtifacts;
|
|
104
|
+
readonly executed: ExecutedTestPlan;
|
|
105
|
+
readonly url: string;
|
|
106
|
+
readonly startedAt: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const mapExecutionEvent = (event: ExecutionEvent, context: DiffContext): TestEvent | undefined => {
|
|
110
|
+
switch (event._tag) {
|
|
111
|
+
case "RunStarted":
|
|
112
|
+
return {
|
|
113
|
+
type: "run:started",
|
|
114
|
+
title: event.plan.title,
|
|
115
|
+
baseUrl: Option.getOrUndefined(event.plan.baseUrl),
|
|
116
|
+
};
|
|
117
|
+
case "StepStarted":
|
|
118
|
+
return { type: "step:started", title: event.title };
|
|
119
|
+
case "StepCompleted": {
|
|
120
|
+
const step = context.stepMap.get(event.stepId);
|
|
121
|
+
const index = context.stepIndexMap.get(event.stepId) ?? -1;
|
|
122
|
+
return step
|
|
123
|
+
? {
|
|
124
|
+
type: "step:passed",
|
|
125
|
+
step: buildStepResult(step, context.artifacts.screenshotPaths, index),
|
|
126
|
+
}
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
case "StepFailed": {
|
|
130
|
+
const step = context.stepMap.get(event.stepId);
|
|
131
|
+
const index = context.stepIndexMap.get(event.stepId) ?? -1;
|
|
132
|
+
return step
|
|
133
|
+
? {
|
|
134
|
+
type: "step:failed",
|
|
135
|
+
step: buildStepResult(step, context.artifacts.screenshotPaths, index),
|
|
136
|
+
}
|
|
137
|
+
: undefined;
|
|
138
|
+
}
|
|
139
|
+
case "StepSkipped":
|
|
140
|
+
return {
|
|
141
|
+
type: "step:skipped",
|
|
142
|
+
title: context.stepMap.get(event.stepId)?.title ?? event.stepId,
|
|
143
|
+
reason: event.reason,
|
|
144
|
+
};
|
|
145
|
+
case "ToolResult":
|
|
146
|
+
return event.toolName.endsWith("__screenshot") && !event.isError
|
|
147
|
+
? { type: "screenshot", title: event.toolName, path: event.result }
|
|
148
|
+
: undefined;
|
|
149
|
+
case "RunFinished":
|
|
150
|
+
return {
|
|
151
|
+
type: "completed",
|
|
152
|
+
result: buildTestResult(
|
|
153
|
+
context.executed,
|
|
154
|
+
context.url,
|
|
155
|
+
context.startedAt,
|
|
156
|
+
context.artifacts,
|
|
157
|
+
),
|
|
158
|
+
};
|
|
159
|
+
default:
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export const diffEvents = (
|
|
165
|
+
previous: readonly ExecutionEvent[],
|
|
166
|
+
current: readonly ExecutionEvent[],
|
|
167
|
+
executed: ExecutedTestPlan,
|
|
168
|
+
url: string,
|
|
169
|
+
startedAt: number,
|
|
170
|
+
): TestEvent[] => {
|
|
171
|
+
const context: DiffContext = {
|
|
172
|
+
stepMap: new Map(executed.steps.map((step) => [step.id, step])),
|
|
173
|
+
stepIndexMap: new Map(executed.steps.map((step, index) => [step.id, index])),
|
|
174
|
+
artifacts: extractArtifacts(executed.events),
|
|
175
|
+
executed,
|
|
176
|
+
url,
|
|
177
|
+
startedAt,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
return current
|
|
181
|
+
.slice(previous.length)
|
|
182
|
+
.map((event) => mapExecutionEvent(event, context))
|
|
183
|
+
.filter((event): event is TestEvent => event !== undefined);
|
|
184
|
+
};
|