{"id":18654676,"url":"https://github.com/juampi92/test-seo","last_synced_at":"2025-04-09T10:09:10.775Z","repository":{"id":58084477,"uuid":"529431997","full_name":"juampi92/test-seo","owner":"juampi92","description":null,"archived":false,"fork":false,"pushed_at":"2024-12-10T15:39:50.000Z","size":43,"stargazers_count":23,"open_issues_count":2,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-02T09:07:52.858Z","etag":null,"topics":["php8","phpunit","seo","testing"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juampi92.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-26T23:34:54.000Z","updated_at":"2024-12-10T15:40:32.000Z","dependencies_parsed_at":"2024-12-10T16:24:07.433Z","dependency_job_id":"db069d25-6a66-4195-ba23-e70a28ba0875","html_url":"https://github.com/juampi92/test-seo","commit_stats":{"total_commits":34,"total_committers":3,"mean_commits":"11.333333333333334","dds":"0.20588235294117652","last_synced_commit":"60cc34e183f374aeaf3df18bf572a74f41bdcbf0"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Ftest-seo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Ftest-seo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Ftest-seo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juampi92%2Ftest-seo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juampi92","download_url":"https://codeload.github.com/juampi92/test-seo/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248018061,"owners_count":21034048,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php8","phpunit","seo","testing"],"created_at":"2024-11-07T07:16:11.449Z","updated_at":"2025-04-09T10:09:10.742Z","avatar_url":"https://github.com/juampi92.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Test SEO\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/juampi92/test-seo.svg?style=flat-square)](https://packagist.org/packages/juampi92/test-seo)\n[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/juampi92/test-seo/run-tests.yml?branch=main\u0026label=tests\u0026style=flat-square)](https://github.com/juampi92/test-seo/actions?query=workflow%3Arun-tests+branch%3Amain)\n[![GitHub Code Style Action Status](https://img.shields.io/github/actions/workflow/status/juampi92/test-seo/pint.yml?branch=main\u0026label=code-style\u0026style=flat-square)](https://github.com/juampi92/test-seo/actions?query=workflow%3A\"Fix+PHP+code+style+issues\"+branch%3Amain)\n[![Total Downloads](https://img.shields.io/packagist/dt/juampi92/test-seo.svg?style=flat-square)](https://packagist.org/packages/juampi92/test-seo)\n\nAn easy-to-use package for testing SEO. The package allows you to extract SEO tags from a given HTML and verify that the SEO structure is correct.\n\n- [Installation](#instalation)\n- [Usage](#usage)\n  - [PHPUnit](#phpunit)\n  - [Laravel](#laravel)\n  - [Pest](#pest)\n- [API](#seo-data)\n  - [SEO Data](#seo-data)\n  - [Assertions](#sssertions)\n- [Snapshot testing](#snapshots)\n\n----\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require juampi92/test-seo --dev\n```\n\n## Usage\n\n```php\n// Create TestSEO instance using the response:\n$seo = new TestSEO($htmlResponse);\n\n// Perform assertions:\n$seo-\u003eassertTitleEndsWith(' - My Website');\n\n// Assert the data yourself:\n$this-\u003eassertEquals(\n    'My title - My Website',\n    $seo-\u003edata-\u003etitle()\n);\n```\n\nLook at the following examples using **PHPUnit**, **Laravel**, and **Pest**.\n\n### PHPUnit\n\n```php\npublic function testLandingPageSEO()\n{\n    // Arrange\n    // ...\n    \n    // Act\n    $response = $client-\u003eget('/')-\u003esend();\n\n    // Assert\n    $this-\u003eassertEquals(200, $response-\u003egetStatusCode());\n    $html = json_decode($response-\u003egetBody(true), true);\n\n    $seo = new TestSEO($html);\n    \n    // Assert\n    $seo\n        -\u003eassertTitleEndsWith(' - My Website')\n        -\u003eassertCanonicalIs('https://www.mywebsite.com/');\n}\n```\n\n### Laravel\n\n```php\npublic function test_landing_page_SEO()\n{\n    // Arrange\n    // ...\n    \n    // Act\n    $response = $this-\u003eget('/');\n\n    // Assert\n    $response-\u003eassertStatus(200);\n\n    $seo = new TestSEO($response-\u003egetContent());\n    \n    $seo\n        -\u003eassertTitleEndsWith(' - My Website')\n        -\u003eassertCanonicalIs('https://www.mywebsite.com/');\n}\n```\n\n### Pest\n\n```php\ntest('landing page SEO tags', function () {\n    // Arrange\n    // ...\n    \n    // Act\n    $response = get('/')-\u003eassertStatus(200);\n    \n    $seo = new TestSEO($response-\u003egetContent());\n    \n    // Assert\n    expect($seo-\u003edata)\n        -\u003etitle()-\u003etoEndWith(' - My Website')\n        -\u003edescription()-\u003etoBe('This is my description')\n        -\u003ecanonical()-\u003enot()-\u003etoBeNull()\n        -\u003erobots()-\u003eindex()-\u003etoBeTrue()\n        -\u003erobots()-\u003enofollow()-\u003etoBeTrue();\n});\n```\n\n## SEO Data\n\nYou can access the SEO Data yourself by accessing the public property `TestSEO-\u003edata`.\nHere are the available methods:\n\n| Method                | Returns                                                                                                                     | Description                                                 |\n|-----------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------|\n| `title()`             | `?string`                                                                                                                   | `\u003ctitle\u003e{this}\u003c/title\u003e`                                     |\n| `description()`       | `?string`                                                                                                                   | `\u003cmeta name=\"description\" content=\"{this}\"\u003e`                |\n| `image()`             | `?Url` [🔍](https://github.com/spatie/url)                                                                                                                   | `\u003cmeta name=\"image\" content=\"{this}\"\u003e`                      |\n| `robots()`            | `Robots` [🔍](https://github.com/juampi92/test-seo/blob/main/src/Tags/Robots.php)                                           | `\u003cmeta name=\"robots\" content=\"{this}\"\u003e`                     |\n| `canonical()`         | `?Url` [🔍](https://github.com/spatie/url)                                                                                  | `\u003clink rel=\"canonical\" href=\"{this}\"\u003e`                      |\n| `prev()`              | `?Url` [🔍](https://github.com/spatie/url)                                                                                  | `\u003clink rel=\"prev\" href=\"{this}\"\u003e`                           |\n| `next()`              | `?Url` [🔍](https://github.com/spatie/url)                                                                                  | `\u003clink rel=\"next\" href=\"{this}\"\u003e`                           |\n| `openGraph()`         | `TagCollection` [🔍](https://github.com/juampi92/test-seo/blob/main/src/Tags/TagCollection.php)                             | `\u003cmeta property=\"og:{key}\" content=\"{value}\"\u003e`                  |\n| `twitter()`           | `TagCollection` [🔍](https://github.com/juampi92/test-seo/blob/main/src/Tags/TagCollection.php)                             | `\u003cmeta name=\"twitter:{key}\" content=\"{value}\"\u003e`             |\n| `alternateHrefLang()` | `AlternateHrefLangCollection` [🔍](https://github.com/juampi92/test-seo/blob/main/src/Tags/AlternateHrefLangCollection.php) | `\u003clink name=\"alternate\" hreflang=\"{hreflang}\" href={href}\u003e` |\n| `images()`            | `array\u003carray{src: string, alt: string, title: string}\u003e`                                                                     | All images in the page. `\u003cimg src=\"...\"\u003e`                   |\n| `h1s()`               | `array\u003cstring\u003e`                                                                                                             | All H1 in the page. `\u003ch1\u003e{this}\u003c/h1\u003e`                       |\n| `h2s()`               | `array\u003cstring\u003e`                                                                                                             | All H2 in the page. `\u003ch2\u003e{this}\u003c/h2\u003e`                       |\n| `charset()`           | `?string`                                                                                                                   | `\u003cmeta charset=\"utf-8\"\u003e`                                    |\n\nThe SEOData class is **Macroable**, so feel free to extend it yourself. \n\n## Assertions\n\n| Method                                  | Notes                                                                                                     |\n|-----------------------------------------|-----------------------------------------------------------------------------------------------------------|\n| `assertCanonicalIs(string $expected)`   |                                                                                                           |\n| `assertCanonicalIsEmpty()`              |                                                                                                           |\n| `assertRobotsIsEmpty()`                 |                                                                                                           |\n| `assertRobotsIsNoIndexNoFollow()`       | Checks that the robots are `noindex, nofollo` or `none`                                                   |\n| `assertPaginationIsEmpty()`             | `prev` and `next` are both missing.                                                                       |\n| `assertAlternateHrefLangIsEmpty()`      |                                                                                                           |\n| `assertTitleIs(string $expected)`       |                                                                                                           |\n| `assertTitleContains(string $expected)` |                                                                                                           |\n| `assertTitleEndsWith(string $expected)` |                                                                                                           |\n| `assertDescriptionIs(string $expected)` |                                                                                                           |\n| `assertThereIsOnlyOneH1()`              | Make sure there is only one H1 in the entire website.                                                     |\n| `assertAllImagesHaveAltText()`          | Make sure all images have an `alt=\"...\"`                                                                  |\n| Suggest your own!                       | These assertions can help devs to follow the best SEO practices. Make a PR if you think some are missing! |\n\n## Snapshots\n\nWhen it comes to SEO, a snapshot test is a great way to ensure nothing has been changed by accident.\n\nHere is an example:\n\n```php\n$seo = new TestSEO($response-\u003egetContent(), snapshotSerializer: null);\n\n$json = json_encode($seo);\n```\n\nBy default, the SEO tags are serialized using the `SimpleSerializer`.\nMake your own serializer by implementing the `SnapshotSerializer` interface:\n\n```php\n$seo = new TestSEO($response-\u003egetContent(), new MyCustomSerializer());\n\n$json = json_encode($seo);\n```\n\n### Pest Example\n\n```php\nuse function Spatie\\Snapshots\\{assertMatchesSnapshot, assertMatchesJsonSnapshot};\nuse Juampi92\\TestSEO\\TestSEO;\n\ntest('landing page SEO', function () {\n    $response = $this-\u003eget('/');\n\n    $response-\u003eassertStatus(200);\n\n    $seo = new TestSEO($response-\u003egetContent());\n\n    assertMatchesJsonSnapshot(json_encode($seo));\n});\n```\n\n**Note:** this example requires `spatie/pest-plugin-snapshots`.\n\n## Contributing\n\nPlease see [CONTRIBUTING](CONTRIBUTING.md) for details.\n\n## Credits\n\n- [Juan Pablo Barreto](https://github.com/juampi92)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuampi92%2Ftest-seo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuampi92%2Ftest-seo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuampi92%2Ftest-seo/lists"}