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/test-run.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TestResult, TestEvent, TestRun } from "./types";
|
|
2
|
+
|
|
3
|
+
interface TestRunCallbacks {
|
|
4
|
+
readonly promise: Promise<TestResult>;
|
|
5
|
+
readonly subscribe: () => AsyncIterableIterator<TestEvent>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const createTestRun = (callbacks: TestRunCallbacks): TestRun => ({
|
|
9
|
+
then<TResult1 = TestResult, TResult2 = never>(
|
|
10
|
+
onfulfilled?: ((value: TestResult) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
|
11
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | undefined | null,
|
|
12
|
+
): PromiseLike<TResult1 | TResult2> {
|
|
13
|
+
return callbacks.promise.then(onfulfilled, onrejected);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
[Symbol.asyncIterator](): AsyncIterator<TestEvent> {
|
|
17
|
+
return callbacks.subscribe();
|
|
18
|
+
},
|
|
19
|
+
});
|
package/src/tool.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import type { Tool } from "./types";
|
|
3
|
+
|
|
4
|
+
type SchemaInput = StandardJSONSchemaV1 | Record<string, unknown>;
|
|
5
|
+
|
|
6
|
+
const isStandardSchema = (schema: SchemaInput): schema is StandardJSONSchemaV1 =>
|
|
7
|
+
"~standard" in schema && typeof (schema as Record<string, unknown>)["~standard"] === "object";
|
|
8
|
+
|
|
9
|
+
const toInputSchema = (schema: SchemaInput): Tool["inputSchema"] => {
|
|
10
|
+
if (isStandardSchema(schema)) {
|
|
11
|
+
return schema["~standard"].jsonSchema.input({ target: "draft-07" }) as Tool["inputSchema"];
|
|
12
|
+
}
|
|
13
|
+
return schema as Tool["inputSchema"];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const tool = (
|
|
17
|
+
name: string,
|
|
18
|
+
description: string,
|
|
19
|
+
schema: SchemaInput,
|
|
20
|
+
handler: (input: Record<string, unknown>) => Promise<string>,
|
|
21
|
+
): Tool => ({
|
|
22
|
+
name,
|
|
23
|
+
description,
|
|
24
|
+
inputSchema: toInputSchema(schema),
|
|
25
|
+
handler,
|
|
26
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Page, BrowserContext, Cookie } from "playwright";
|
|
2
|
+
import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
|
|
4
|
+
export type { Cookie };
|
|
5
|
+
|
|
6
|
+
export type Action = string | ((page: Page) => Promise<void | string>);
|
|
7
|
+
|
|
8
|
+
export type BrowserName = "chrome" | "firefox" | "safari" | "edge" | "brave" | "arc";
|
|
9
|
+
|
|
10
|
+
export type CookieInput = true | BrowserName | BrowserName[] | Cookie[];
|
|
11
|
+
|
|
12
|
+
export type Test = string | { title?: string; prompt: string };
|
|
13
|
+
|
|
14
|
+
export type Status = "pending" | "passed" | "failed";
|
|
15
|
+
|
|
16
|
+
export interface Tool extends Pick<McpTool, "name" | "description" | "inputSchema"> {
|
|
17
|
+
readonly handler: (input: Record<string, unknown>) => Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StepResult {
|
|
21
|
+
readonly title: string;
|
|
22
|
+
readonly status: Status;
|
|
23
|
+
readonly summary: string;
|
|
24
|
+
readonly screenshotPath?: string;
|
|
25
|
+
readonly duration: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface TestResult {
|
|
29
|
+
readonly status: Status;
|
|
30
|
+
readonly url: string;
|
|
31
|
+
readonly duration: number;
|
|
32
|
+
readonly recordingPath?: string;
|
|
33
|
+
readonly steps: StepResult[];
|
|
34
|
+
readonly errors: StepResult[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type TestEvent =
|
|
38
|
+
| { readonly type: "run:started"; readonly title: string; readonly baseUrl?: string }
|
|
39
|
+
| { readonly type: "step:started"; readonly title: string }
|
|
40
|
+
| { readonly type: "step:passed"; readonly step: StepResult }
|
|
41
|
+
| { readonly type: "step:failed"; readonly step: StepResult }
|
|
42
|
+
| { readonly type: "step:skipped"; readonly title: string; readonly reason: string }
|
|
43
|
+
| { readonly type: "screenshot"; readonly title: string; readonly path: string }
|
|
44
|
+
| { readonly type: "completed"; readonly result: TestResult };
|
|
45
|
+
|
|
46
|
+
export interface TestRun extends PromiseLike<TestResult> {
|
|
47
|
+
[Symbol.asyncIterator](): AsyncIterator<TestEvent>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface TestInput {
|
|
51
|
+
readonly url?: string;
|
|
52
|
+
readonly page?: Page;
|
|
53
|
+
readonly cookies?: CookieInput;
|
|
54
|
+
readonly tools?: Tool[];
|
|
55
|
+
readonly tests: Test[];
|
|
56
|
+
readonly before?: Action;
|
|
57
|
+
readonly after?: Action;
|
|
58
|
+
readonly mode?: "headed" | "headless";
|
|
59
|
+
readonly timeout?: number;
|
|
60
|
+
readonly isRecording?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface SessionConfig {
|
|
64
|
+
readonly url?: string;
|
|
65
|
+
readonly browserContext?: BrowserContext;
|
|
66
|
+
readonly cookies?: CookieInput;
|
|
67
|
+
readonly tools?: Tool[];
|
|
68
|
+
readonly mode?: "headed" | "headless";
|
|
69
|
+
readonly timeout?: number;
|
|
70
|
+
readonly isRecording?: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface SessionTestInput {
|
|
74
|
+
readonly url?: string;
|
|
75
|
+
readonly tests: Test[];
|
|
76
|
+
readonly before?: Action;
|
|
77
|
+
readonly after?: Action;
|
|
78
|
+
readonly mode?: "headed" | "headless";
|
|
79
|
+
readonly timeout?: number;
|
|
80
|
+
readonly isRecording?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ExpectSession {
|
|
84
|
+
test(input: SessionTestInput): TestRun;
|
|
85
|
+
close(): Promise<void>;
|
|
86
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface ExpectConfig {
|
|
90
|
+
readonly baseUrl?: string;
|
|
91
|
+
readonly browser?: BrowserName;
|
|
92
|
+
readonly mode?: "headed" | "headless";
|
|
93
|
+
readonly cookies?: CookieInput;
|
|
94
|
+
readonly timeout?: number;
|
|
95
|
+
readonly model?: string;
|
|
96
|
+
readonly apiKey?: string;
|
|
97
|
+
readonly rootDir?: string;
|
|
98
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect } from "vite-plus/test";
|
|
2
|
+
import { resolveUrl, buildInstruction } from "../src/build-instruction";
|
|
3
|
+
|
|
4
|
+
describe("resolveUrl", () => {
|
|
5
|
+
it("passes through absolute http URLs", () => {
|
|
6
|
+
expect(resolveUrl("http://localhost:3000/login", undefined)).toBe(
|
|
7
|
+
"http://localhost:3000/login",
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("passes through absolute https URLs", () => {
|
|
12
|
+
expect(resolveUrl("https://example.com/page", undefined)).toBe("https://example.com/page");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("resolves relative URL against baseUrl", () => {
|
|
16
|
+
expect(resolveUrl("/login", "http://localhost:3000")).toBe("http://localhost:3000/login");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("resolves relative URL without leading slash", () => {
|
|
20
|
+
expect(resolveUrl("login", "http://localhost:3000")).toBe("http://localhost:3000/login");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("strips trailing slash from baseUrl", () => {
|
|
24
|
+
expect(resolveUrl("/login", "http://localhost:3000/")).toBe("http://localhost:3000/login");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("ignores baseUrl for absolute URLs", () => {
|
|
28
|
+
expect(resolveUrl("http://other.com/page", "http://localhost:3000")).toBe(
|
|
29
|
+
"http://other.com/page",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("throws for non-string URL", () => {
|
|
34
|
+
expect(() => resolveUrl(undefined, undefined)).toThrow("Expected a URL string");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("throws for relative URL without baseUrl", () => {
|
|
38
|
+
expect(() => resolveUrl("/login", undefined)).toThrow("No baseUrl configured");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("includes Fix line in error", () => {
|
|
42
|
+
try {
|
|
43
|
+
resolveUrl("/login", undefined);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
expect((error as Error).message).toContain("Fix:");
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("buildInstruction", () => {
|
|
51
|
+
it("builds single test", () => {
|
|
52
|
+
const result = buildInstruction("http://localhost:3000/login", ["login works"]);
|
|
53
|
+
expect(result).toContain("1. login works");
|
|
54
|
+
expect(result).toContain("Navigate to http://localhost:3000/login");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("numbers multiple tests", () => {
|
|
58
|
+
const result = buildInstruction("http://localhost:3000", ["test one", "test two"]);
|
|
59
|
+
expect(result).toContain("1. test one");
|
|
60
|
+
expect(result).toContain("2. test two");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vite-plus/test";
|
|
2
|
+
import { defineConfig, configure, getGlobalConfig, resetGlobalConfig } from "../src/config";
|
|
3
|
+
|
|
4
|
+
describe("defineConfig", () => {
|
|
5
|
+
it("returns the same reference", () => {
|
|
6
|
+
const config = { baseUrl: "http://localhost:3000" };
|
|
7
|
+
expect(defineConfig(config)).toBe(config);
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("configure", () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resetGlobalConfig();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("merges into global config", () => {
|
|
17
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
18
|
+
expect(getGlobalConfig().baseUrl).toBe("http://localhost:3000");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("merges successive calls", () => {
|
|
22
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
23
|
+
configure({ mode: "headed" });
|
|
24
|
+
const config = getGlobalConfig();
|
|
25
|
+
expect(config.baseUrl).toBe("http://localhost:3000");
|
|
26
|
+
expect(config.mode).toBe("headed");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("later values override earlier ones", () => {
|
|
30
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
31
|
+
configure({ baseUrl: "http://localhost:4000" });
|
|
32
|
+
expect(getGlobalConfig().baseUrl).toBe("http://localhost:4000");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("resetGlobalConfig", () => {
|
|
37
|
+
it("clears all values", () => {
|
|
38
|
+
configure({ baseUrl: "http://localhost:3000", mode: "headed" });
|
|
39
|
+
resetGlobalConfig();
|
|
40
|
+
const config = getGlobalConfig();
|
|
41
|
+
expect(config.baseUrl).toBeUndefined();
|
|
42
|
+
expect(config.mode).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
package/tests/e2e.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Expect } from "../src/expect";
|
|
2
|
+
import { configure, resetGlobalConfig } from "../src/config";
|
|
3
|
+
import { startFixtureServer } from "./fixtures/fixture-server";
|
|
4
|
+
import type { TestEvent } from "../src/types";
|
|
5
|
+
|
|
6
|
+
const server = await startFixtureServer();
|
|
7
|
+
const fixtureUrl = server.url;
|
|
8
|
+
|
|
9
|
+
console.log(`Fixture server started at ${fixtureUrl}`);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
console.log("\n--- test: page loads correctly ---");
|
|
13
|
+
const basic = await Expect.test({
|
|
14
|
+
url: fixtureUrl,
|
|
15
|
+
tests: ["the page loads and shows a heading"],
|
|
16
|
+
});
|
|
17
|
+
console.log(`Status: ${basic.status} (${basic.steps.length} steps)`);
|
|
18
|
+
for (const step of basic.steps) {
|
|
19
|
+
console.log(` ${step.status === "passed" ? "✅" : "❌"} ${step.title}`);
|
|
20
|
+
}
|
|
21
|
+
if (basic.status !== "passed") throw new Error("basic test failed");
|
|
22
|
+
|
|
23
|
+
console.log("\n--- test: streaming events ---");
|
|
24
|
+
const run = Expect.test({
|
|
25
|
+
url: fixtureUrl,
|
|
26
|
+
tests: ["the main heading says Welcome"],
|
|
27
|
+
});
|
|
28
|
+
const events: TestEvent[] = [];
|
|
29
|
+
for await (const event of run) {
|
|
30
|
+
events.push(event);
|
|
31
|
+
if (event.type === "step:passed") console.log(` ✅ ${event.step.title}`);
|
|
32
|
+
if (event.type === "step:failed") console.log(` ❌ ${event.step.title}`);
|
|
33
|
+
}
|
|
34
|
+
const streamResult = await run;
|
|
35
|
+
console.log(`Status: ${streamResult.status} (${events.length} events)`);
|
|
36
|
+
if (events.length === 0) throw new Error("no events received");
|
|
37
|
+
|
|
38
|
+
console.log("\n--- test: login page ---");
|
|
39
|
+
const loginResult = await Expect.test({
|
|
40
|
+
url: `${fixtureUrl}/login`,
|
|
41
|
+
tests: ["the login page has an email field and a password field"],
|
|
42
|
+
});
|
|
43
|
+
console.log(`Status: ${loginResult.status}`);
|
|
44
|
+
|
|
45
|
+
console.log("\n--- test: configure baseUrl ---");
|
|
46
|
+
configure({ baseUrl: fixtureUrl });
|
|
47
|
+
const signupResult = await Expect.test({
|
|
48
|
+
url: "/signup",
|
|
49
|
+
tests: ["the signup page has email, password, and confirm password fields"],
|
|
50
|
+
});
|
|
51
|
+
console.log(`Status: ${signupResult.status}`);
|
|
52
|
+
resetGlobalConfig();
|
|
53
|
+
|
|
54
|
+
console.log("\n--- test: session ---");
|
|
55
|
+
const session = Expect.session({ url: fixtureUrl });
|
|
56
|
+
const homeResult = await session.test({
|
|
57
|
+
url: fixtureUrl,
|
|
58
|
+
tests: ["the homepage has navigation links"],
|
|
59
|
+
});
|
|
60
|
+
console.log(`Home: ${homeResult.status}`);
|
|
61
|
+
const sessionLoginResult = await session.test({
|
|
62
|
+
url: `${fixtureUrl}/login`,
|
|
63
|
+
tests: ["the login page has a sign-in form"],
|
|
64
|
+
});
|
|
65
|
+
console.log(`Login: ${sessionLoginResult.status}`);
|
|
66
|
+
await session.close();
|
|
67
|
+
|
|
68
|
+
const allPassed = [
|
|
69
|
+
basic,
|
|
70
|
+
streamResult,
|
|
71
|
+
loginResult,
|
|
72
|
+
signupResult,
|
|
73
|
+
homeResult,
|
|
74
|
+
sessionLoginResult,
|
|
75
|
+
].every((result) => result.status === "passed");
|
|
76
|
+
|
|
77
|
+
console.log(`\nAll passed: ${allPassed}`);
|
|
78
|
+
process.exit(allPassed ? 0 : 1);
|
|
79
|
+
} finally {
|
|
80
|
+
await server.close();
|
|
81
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vite-plus/test";
|
|
2
|
+
import { configure, resetGlobalConfig } from "../src/config";
|
|
3
|
+
import { resolveUrl, buildInstruction } from "../src/build-instruction";
|
|
4
|
+
import { Expect } from "../src/expect";
|
|
5
|
+
|
|
6
|
+
describe("example patterns — validation only", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resetGlobalConfig();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("basic: resolves absolute url", () => {
|
|
12
|
+
const url = resolveUrl("http://localhost:3000", undefined);
|
|
13
|
+
expect(url).toBe("http://localhost:3000/");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("auth: configure baseUrl resolves relative urls", () => {
|
|
17
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
18
|
+
const loginUrl = resolveUrl("/login", "http://localhost:3000");
|
|
19
|
+
const signupUrl = resolveUrl("/signup", "http://localhost:3000");
|
|
20
|
+
expect(loginUrl).toBe("http://localhost:3000/login");
|
|
21
|
+
expect(signupUrl).toBe("http://localhost:3000/signup");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("session: creates session with test and close methods", () => {
|
|
25
|
+
const session = Expect.session({ url: "http://localhost:3000" });
|
|
26
|
+
expect(typeof session.test).toBe("function");
|
|
27
|
+
expect(typeof session.close).toBe("function");
|
|
28
|
+
expect(typeof session[Symbol.asyncDispose]).toBe("function");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("instruction: builds numbered test list", () => {
|
|
32
|
+
const instruction = buildInstruction("http://localhost:3000/login", [
|
|
33
|
+
"signing in with valid credentials redirects to the dashboard",
|
|
34
|
+
"the dashboard shows the user's name",
|
|
35
|
+
]);
|
|
36
|
+
expect(instruction).toContain("1. signing in");
|
|
37
|
+
expect(instruction).toContain("2. the dashboard");
|
|
38
|
+
expect(instruction).toContain("http://localhost:3000/login");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("cookies: Expect.cookies is callable", () => {
|
|
42
|
+
expect(typeof Expect.cookies).toBe("function");
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vite-plus/test";
|
|
2
|
+
import { resetGlobalConfig, configure } from "../src/config";
|
|
3
|
+
import { Expect } from "../src/expect";
|
|
4
|
+
import { default as ExpectDefault } from "../src/index";
|
|
5
|
+
|
|
6
|
+
describe("Expect.test input validation", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resetGlobalConfig();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("throws for empty tests array", () => {
|
|
12
|
+
expect(() => Expect.test({ url: "http://localhost:3000", tests: [] })).toThrow(
|
|
13
|
+
"tests array is empty",
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("throws when no URL and no baseUrl", () => {
|
|
18
|
+
expect(() => Expect.test({ tests: ["something"] })).toThrow("No URL provided");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("throws for relative URL without baseUrl", () => {
|
|
22
|
+
expect(() => Expect.test({ url: "/login", tests: ["something"] })).toThrow(
|
|
23
|
+
"No baseUrl configured",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("does not throw when baseUrl is configured", () => {
|
|
28
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
29
|
+
const run = Expect.test({ url: "/login", tests: ["something"] });
|
|
30
|
+
run.then(
|
|
31
|
+
() => {},
|
|
32
|
+
() => {},
|
|
33
|
+
);
|
|
34
|
+
expect(run).toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("uses baseUrl when no url provided", () => {
|
|
38
|
+
configure({ baseUrl: "http://localhost:3000" });
|
|
39
|
+
const run = Expect.test({ tests: ["something"] });
|
|
40
|
+
run.then(
|
|
41
|
+
() => {},
|
|
42
|
+
() => {},
|
|
43
|
+
);
|
|
44
|
+
expect(run).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("throws when function before has no page", () => {
|
|
48
|
+
expect(() =>
|
|
49
|
+
Expect.test({
|
|
50
|
+
url: "http://localhost:3000",
|
|
51
|
+
tests: ["something"],
|
|
52
|
+
before: async () => {},
|
|
53
|
+
}),
|
|
54
|
+
).toThrow("Function before requires a page");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("throws when function after has no page", () => {
|
|
58
|
+
expect(() =>
|
|
59
|
+
Expect.test({
|
|
60
|
+
url: "http://localhost:3000",
|
|
61
|
+
tests: ["something"],
|
|
62
|
+
after: async () => {},
|
|
63
|
+
}),
|
|
64
|
+
).toThrow("Function after requires a page");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("throws when tools are provided", () => {
|
|
68
|
+
expect(() =>
|
|
69
|
+
Expect.test({
|
|
70
|
+
url: "http://localhost:3000",
|
|
71
|
+
tests: ["something"],
|
|
72
|
+
tools: [
|
|
73
|
+
{
|
|
74
|
+
name: "t",
|
|
75
|
+
description: "d",
|
|
76
|
+
inputSchema: { type: "object" as const },
|
|
77
|
+
handler: async () => "",
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
}),
|
|
81
|
+
).toThrow("Custom tools are not yet supported");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("Expect.session", () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
resetGlobalConfig();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("creates a session with test method", () => {
|
|
91
|
+
const session = Expect.session({ url: "http://localhost:3000" });
|
|
92
|
+
expect(typeof session.test).toBe("function");
|
|
93
|
+
expect(typeof session.close).toBe("function");
|
|
94
|
+
expect(typeof session[Symbol.asyncDispose]).toBe("function");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("session.test throws for empty tests", () => {
|
|
98
|
+
const session = Expect.session({ url: "http://localhost:3000" });
|
|
99
|
+
expect(() => session.test({ tests: [] })).toThrow("tests array is empty");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("session.close resolves", async () => {
|
|
103
|
+
const session = Expect.session({ url: "http://localhost:3000" });
|
|
104
|
+
await expect(session.close()).resolves.toBeUndefined();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("throws when browserContext is provided", () => {
|
|
108
|
+
expect(() =>
|
|
109
|
+
Expect.session({
|
|
110
|
+
url: "http://localhost:3000",
|
|
111
|
+
browserContext: {} as never,
|
|
112
|
+
}),
|
|
113
|
+
).toThrow("External browserContext is not yet supported");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("throws when tools are provided", () => {
|
|
117
|
+
expect(() =>
|
|
118
|
+
Expect.session({
|
|
119
|
+
url: "http://localhost:3000",
|
|
120
|
+
tools: [
|
|
121
|
+
{
|
|
122
|
+
name: "t",
|
|
123
|
+
description: "d",
|
|
124
|
+
inputSchema: { type: "object" as const },
|
|
125
|
+
handler: async () => "",
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
).toThrow("Custom tools are not yet supported");
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Expect.cookies", () => {
|
|
134
|
+
it("is a function", () => {
|
|
135
|
+
expect(typeof Expect.cookies).toBe("function");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("default export", () => {
|
|
140
|
+
it("Expect is available as default export", () => {
|
|
141
|
+
expect(ExpectDefault).toBe(Expect);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import type { AddressInfo } from "node:net";
|
|
3
|
+
|
|
4
|
+
const INDEX_HTML = `<!DOCTYPE html>
|
|
5
|
+
<html lang="en">
|
|
6
|
+
<head><meta charset="UTF-8"><title>Acme App</title></head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Welcome to Acme</h1>
|
|
9
|
+
<p>Your dashboard for everything.</p>
|
|
10
|
+
<nav>
|
|
11
|
+
<a href="/login">Login</a>
|
|
12
|
+
<a href="/signup">Signup</a>
|
|
13
|
+
</nav>
|
|
14
|
+
</body>
|
|
15
|
+
</html>`;
|
|
16
|
+
|
|
17
|
+
const LOGIN_HTML = `<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head><meta charset="UTF-8"><title>Login - Acme</title></head>
|
|
20
|
+
<body>
|
|
21
|
+
<h1>Sign in to Acme</h1>
|
|
22
|
+
<form id="login-form">
|
|
23
|
+
<div>
|
|
24
|
+
<label for="email">Email</label>
|
|
25
|
+
<input id="email" type="email" required placeholder="you@example.com" />
|
|
26
|
+
<span class="error" id="email-error" style="display:none;color:red">Email is required</span>
|
|
27
|
+
</div>
|
|
28
|
+
<div>
|
|
29
|
+
<label for="password">Password</label>
|
|
30
|
+
<input id="password" type="password" required placeholder="••••••••" />
|
|
31
|
+
<span class="error" id="password-error" style="display:none;color:red">Password is required</span>
|
|
32
|
+
</div>
|
|
33
|
+
<button type="submit">Sign in</button>
|
|
34
|
+
</form>
|
|
35
|
+
<div id="server-error" style="display:none;color:red">Invalid email or password</div>
|
|
36
|
+
<p>Don't have an account? <a href="/signup">Sign up</a></p>
|
|
37
|
+
<script>
|
|
38
|
+
document.getElementById('login-form').addEventListener('submit', function(e) {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
var email = document.getElementById('email').value;
|
|
41
|
+
var password = document.getElementById('password').value;
|
|
42
|
+
var hasError = false;
|
|
43
|
+
if (!email) { document.getElementById('email-error').style.display = 'block'; hasError = true; }
|
|
44
|
+
else { document.getElementById('email-error').style.display = 'none'; }
|
|
45
|
+
if (!password) { document.getElementById('password-error').style.display = 'block'; hasError = true; }
|
|
46
|
+
else { document.getElementById('password-error').style.display = 'none'; }
|
|
47
|
+
if (hasError) return;
|
|
48
|
+
document.getElementById('server-error').style.display = 'block';
|
|
49
|
+
});
|
|
50
|
+
</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>`;
|
|
53
|
+
|
|
54
|
+
const SIGNUP_HTML = `<!DOCTYPE html>
|
|
55
|
+
<html lang="en">
|
|
56
|
+
<head><meta charset="UTF-8"><title>Signup - Acme</title></head>
|
|
57
|
+
<body>
|
|
58
|
+
<h1>Create an account</h1>
|
|
59
|
+
<form id="signup-form">
|
|
60
|
+
<div>
|
|
61
|
+
<label for="email">Email</label>
|
|
62
|
+
<input id="email" type="email" required />
|
|
63
|
+
</div>
|
|
64
|
+
<div>
|
|
65
|
+
<label for="password">Password</label>
|
|
66
|
+
<input id="password" type="password" required />
|
|
67
|
+
</div>
|
|
68
|
+
<div>
|
|
69
|
+
<label for="confirm">Confirm Password</label>
|
|
70
|
+
<input id="confirm" type="password" required />
|
|
71
|
+
</div>
|
|
72
|
+
<button type="submit">Create account</button>
|
|
73
|
+
<span id="match-error" style="display:none;color:red">Passwords do not match</span>
|
|
74
|
+
</form>
|
|
75
|
+
<p>Already have an account? <a href="/login">Sign in</a></p>
|
|
76
|
+
<script>
|
|
77
|
+
document.getElementById('signup-form').addEventListener('submit', function(e) {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
var pw = document.getElementById('password').value;
|
|
80
|
+
var confirm = document.getElementById('confirm').value;
|
|
81
|
+
if (pw !== confirm) {
|
|
82
|
+
document.getElementById('match-error').style.display = 'block';
|
|
83
|
+
} else {
|
|
84
|
+
document.getElementById('match-error').style.display = 'none';
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
</script>
|
|
88
|
+
</body>
|
|
89
|
+
</html>`;
|
|
90
|
+
|
|
91
|
+
const PAGES: Record<string, string> = {
|
|
92
|
+
"/": INDEX_HTML,
|
|
93
|
+
"/login": LOGIN_HTML,
|
|
94
|
+
"/signup": SIGNUP_HTML,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const startFixtureServer = (): Promise<{ url: string; close: () => Promise<void> }> =>
|
|
98
|
+
new Promise((resolve) => {
|
|
99
|
+
const server = http.createServer((req, res) => {
|
|
100
|
+
const page = PAGES[req.url ?? "/"] ?? INDEX_HTML;
|
|
101
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
102
|
+
res.end(page);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
server.listen(0, "127.0.0.1", () => {
|
|
106
|
+
const { port } = server.address() as AddressInfo;
|
|
107
|
+
const url = `http://127.0.0.1:${port}`;
|
|
108
|
+
const close = () => new Promise<void>((done) => server.close(() => done()));
|
|
109
|
+
resolve({ url, close });
|
|
110
|
+
});
|
|
111
|
+
});
|