Skip to content

Commit 8916c90

Browse files
committed
Add UI tests
1 parent 81624bc commit 8916c90

10 files changed

Lines changed: 375 additions & 26 deletions

File tree

.github/workflows/matomo-tests.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,25 @@ jobs:
5656
redis-service: true
5757
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
5858
upload-artifacts: ${{ matrix.php == '7.4' && matrix.target == 'maximum_supported_matomo' }}
59+
artifacts-protected: true
60+
github-token: ${{ secrets.TESTS_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
61+
UI:
62+
runs-on: ubuntu-24.04
63+
steps:
64+
- uses: actions/checkout@v3
65+
with:
66+
lfs: true
67+
persist-credentials: false
68+
- name: running tests
69+
uses: matomo-org/github-action-tests@main
70+
with:
71+
plugin-name: 'ApiReference'
72+
matomo-test-branch: 'maximum_supported_matomo'
73+
test-type: 'UI'
74+
php-version: 'matomo5_max_php'
75+
node-version: '16'
76+
redis-service: true
77+
artifacts-pass: ${{ secrets.ARTIFACTS_PASS }}
78+
upload-artifacts: true
79+
artifacts-protected: true
80+
github-token: ${{ secrets.TESTS_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
<?php
2+
3+
/**
4+
* Matomo - free/libre analytics platform
5+
*
6+
* @link https://matomo.org
7+
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace Piwik\Plugins\ApiReference\tests\Fixtures;
13+
14+
use Piwik\Plugins\ApiReference\ApiReference;
15+
use Piwik\Plugins\ApiReference\Specs\PathResolver;
16+
use Piwik\Tests\Framework\Fixture;
17+
use Piwik\Updater;
18+
19+
class SwaggerPageFixture extends Fixture
20+
{
21+
public $dateTime = '2010-01-03 00:00:00';
22+
public $idSite = 1;
23+
24+
public function setUp(): void
25+
{
26+
$this->setUpWebsite();
27+
$this->markApiReferenceAsInstalled();
28+
$this->writeOpenApiSpecFixtures();
29+
}
30+
31+
public function tearDown(): void
32+
{
33+
$this->removeOpenApiSpecFixtures();
34+
}
35+
36+
private function setUpWebsite(): void
37+
{
38+
if (!self::siteCreated($this->idSite)) {
39+
$idSite = self::createWebsite($this->dateTime);
40+
$this->assertSame($this->idSite, $idSite);
41+
}
42+
}
43+
44+
private function writeOpenApiSpecFixtures(): void
45+
{
46+
$resolver = new PathResolver();
47+
$specDirectory = $resolver->getSpecDirectory();
48+
49+
if (!is_dir($specDirectory)) {
50+
mkdir($specDirectory, 0777, true);
51+
}
52+
53+
file_put_contents(
54+
$this->getReferrersSpecPath(),
55+
json_encode($this->getReferrersSpec(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
56+
);
57+
}
58+
59+
private function markApiReferenceAsInstalled(): void
60+
{
61+
(new Updater())->markComponentSuccessfullyUpdated('ApiReference', '5.0.0');
62+
}
63+
64+
private function removeOpenApiSpecFixtures(): void
65+
{
66+
$specPath = $this->getReferrersSpecPath();
67+
68+
if (is_file($specPath)) {
69+
unlink($specPath);
70+
}
71+
}
72+
73+
private function getReferrersSpecPath(): string
74+
{
75+
return (new PathResolver())->getSpecFilePath('Referrers', ApiReference::DEFAULT_SPEC_VERSION);
76+
}
77+
78+
/**
79+
* @return array<string, mixed>
80+
*/
81+
private function getReferrersSpec(): array
82+
{
83+
$description = 'Returns example reporting data for referrer types.';
84+
85+
return [
86+
'openapi' => '3.1.0',
87+
'info' => [
88+
'title' => 'Reporting API for Referrers plugin',
89+
'version' => ApiReference::DEFAULT_SPEC_VERSION,
90+
'description' => 'Fixture-backed OpenAPI data for ApiReference UI screenshot tests.',
91+
],
92+
'tags' => [
93+
[
94+
'name' => 'Referrers',
95+
'description' => 'Referrer reporting endpoints.',
96+
],
97+
],
98+
'paths' => [
99+
'/index.php?module=API&method=Referrers.getReferrerType' => [
100+
'get' => [
101+
'tags' => ['Referrers'],
102+
'summary' => 'Get referrer types',
103+
'description' => $description,
104+
'parameters' => [
105+
[
106+
'name' => 'idSite',
107+
'in' => 'query',
108+
'required' => true,
109+
'schema' => ['type' => 'integer'],
110+
],
111+
[
112+
'name' => 'period',
113+
'in' => 'query',
114+
'required' => true,
115+
'schema' => ['type' => 'string'],
116+
],
117+
[
118+
'name' => 'date',
119+
'in' => 'query',
120+
'required' => true,
121+
'schema' => ['type' => 'string'],
122+
],
123+
],
124+
'responses' => [
125+
'200' => [
126+
'description' => 'Successful response',
127+
'content' => [
128+
'application/json' => [
129+
'schema' => [
130+
'type' => 'array',
131+
'items' => [
132+
'type' => 'object',
133+
'properties' => [
134+
'label' => ['type' => 'string'],
135+
'nb_visits' => ['type' => 'integer'],
136+
],
137+
],
138+
],
139+
],
140+
],
141+
],
142+
],
143+
],
144+
],
145+
'/index.php?module=API&method=Referrers.getCampaigns' => [
146+
'post' => [
147+
'tags' => ['Referrers'],
148+
'summary' => 'Get campaigns',
149+
'description' => 'Returns example campaign reporting data.',
150+
'requestBody' => [
151+
'required' => false,
152+
'content' => [
153+
'application/x-www-form-urlencoded' => [
154+
'schema' => [
155+
'type' => 'object',
156+
'properties' => [
157+
'segment' => ['type' => 'string'],
158+
],
159+
],
160+
],
161+
],
162+
],
163+
'responses' => [
164+
'200' => [
165+
'description' => 'Successful response',
166+
'content' => [
167+
'application/json' => [
168+
'schema' => [
169+
'type' => 'array',
170+
'items' => [
171+
'type' => 'object',
172+
'properties' => [
173+
'label' => ['type' => 'string'],
174+
'revenue' => ['type' => 'number'],
175+
],
176+
],
177+
],
178+
],
179+
],
180+
],
181+
],
182+
],
183+
],
184+
],
185+
];
186+
}
187+
}

tests/UI/ApiReference_spec.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*!
2+
* Matomo - free/libre analytics platform
3+
*
4+
* ApiReference screenshot tests.
5+
*
6+
* @link https://matomo.org
7+
* @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8+
*/
9+
10+
describe('ApiReference', function () {
11+
this.fixture = 'Piwik\\Plugins\\ApiReference\\tests\\Fixtures\\SwaggerPageFixture';
12+
13+
const pageUrl = '?module=ApiReference&action=swagger&idSite=1&period=day&date=2010-01-03';
14+
const targetPlugin = 'Referrers';
15+
const searchInputSelector = '.searchInput';
16+
17+
before(function () {
18+
testEnvironment.pluginsToLoad = ['ApiReference'];
19+
testEnvironment.testUseMockAuth = 1;
20+
testEnvironment.overrideConfig('General', 'enable_auto_update', 0);
21+
testEnvironment.save();
22+
});
23+
24+
after(function () {
25+
delete testEnvironment.pluginsToLoad;
26+
delete testEnvironment.configOverride.General;
27+
testEnvironment.testUseMockAuth = 1;
28+
testEnvironment.save();
29+
});
30+
31+
async function moveMouseAway() {
32+
await page.mouse.move(-10, -10);
33+
}
34+
35+
async function waitForUiToSettle(delay = 150) {
36+
await page.waitForNetworkIdle();
37+
await page.waitForTimeout(delay);
38+
}
39+
40+
async function loadSwaggerPage() {
41+
await page.goto(pageUrl);
42+
await page.waitForSelector(searchInputSelector);
43+
await page.waitForSelector('.pluginList .pluginCard');
44+
await waitForUiToSettle();
45+
await moveMouseAway();
46+
}
47+
48+
async function searchFor(term) {
49+
await page.evaluate((selector, value) => {
50+
const input = document.querySelector(selector);
51+
52+
if (!input) {
53+
throw new Error(`Unable to find ${selector}`);
54+
}
55+
56+
input.value = value;
57+
input.dispatchEvent(new Event('input', { bubbles: true }));
58+
input.dispatchEvent(new Event('change', { bubbles: true }));
59+
}, searchInputSelector, term);
60+
await page.waitForTimeout(150);
61+
await moveMouseAway();
62+
}
63+
64+
async function expandPlugin(pluginName) {
65+
await page.evaluate((targetName) => {
66+
const pluginButtons = Array.from(document.querySelectorAll('.pluginToggle'));
67+
const pluginButton = pluginButtons.find((button) => {
68+
const pluginNameElement = button.querySelector('.pluginName');
69+
return pluginNameElement && pluginNameElement.textContent.trim() === targetName;
70+
});
71+
72+
if (!pluginButton) {
73+
throw new Error(`Unable to find plugin card for ${targetName}`);
74+
}
75+
76+
pluginButton.click();
77+
}, pluginName);
78+
79+
await page.waitForSelector('.pluginCard--expanded');
80+
await page.waitForSelector('.pluginCard--expanded .swaggerMount--ready');
81+
await page.waitForSelector('.pluginCard--expanded .swagger-ui .opblock-tag-section');
82+
await waitForUiToSettle(250);
83+
await moveMouseAway();
84+
}
85+
86+
async function waitForSingleVisiblePlugin(pluginName) {
87+
await page.waitForFunction((expectedPluginName) => {
88+
const visiblePluginNames = Array.from(document.querySelectorAll('.pluginCard'))
89+
.filter((card) => card.offsetParent !== null)
90+
.map((card) => card.querySelector('.pluginName')?.textContent?.trim())
91+
.filter(Boolean);
92+
93+
return visiblePluginNames.length === 1 && visiblePluginNames[0] === expectedPluginName;
94+
}, {}, pluginName);
95+
}
96+
97+
it('should render stable search and expansion states', async function () {
98+
await loadSwaggerPage();
99+
await searchFor('referrers');
100+
await waitForSingleVisiblePlugin(targetPlugin);
101+
102+
expect(await page.screenshotSelector('.searchBar,.pluginList')).to.matchImage('filtered_plugin');
103+
104+
await searchFor('no-plugin-match');
105+
await page.waitForSelector('.emptyText');
106+
107+
expect(await page.screenshotSelector('.searchBar,.emptyText')).to.matchImage('empty_search');
108+
109+
await searchFor('referrers');
110+
await waitForSingleVisiblePlugin(targetPlugin);
111+
await expandPlugin(targetPlugin);
112+
113+
expect(await page.screenshotSelector('.pluginCard--expanded')).to.matchImage('expanded_plugin');
114+
});
115+
});
5.92 KB
Loading
18.6 KB
Loading
4.57 KB
Loading

vue/dist/ApiReference.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)