Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web): Improve web e2e tests and move them to their directory #16842

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0f0c704
feat: Move web e2e tests to web app directory
svanaeinars Nov 13, 2024
f626089
Merge branch 'main' into feat/move-and-improve-e2e-tests-web
svanaeinars Nov 14, 2024
9ee7e96
Merge branch 'main' into feat/move-and-improve-e2e-tests-web
svanaeinars Nov 14, 2024
05e489f
Remove e2e from jest and improve tests
svanaeinars Nov 14, 2024
af54dcc
Minor tweaks
svanaeinars Nov 15, 2024
1226759
Small test improvements
svanaeinars Nov 15, 2024
dc24b29
Docs update for web
svanaeinars Nov 15, 2024
c3f0dbf
Minor tweak
svanaeinars Nov 15, 2024
3e2cbf3
Workflow test
svanaeinars Nov 18, 2024
0d9addf
Change web start command e2e playwright
svanaeinars Nov 18, 2024
394c853
Remove Playwright projects and add Tags
svanaeinars Nov 20, 2024
03cdcb7
Merge branch 'main' into feat/move-and-improve-e2e-tests-web
svanaeinars Nov 20, 2024
b946139
Revert workflow test
svanaeinars Nov 20, 2024
0783e04
Minor test updates
svanaeinars Nov 21, 2024
50b5a4d
Merge branch 'main' into feat/move-and-improve-e2e-tests-web
svanaeinars Nov 21, 2024
d2393a5
Docs update for web
svanaeinars Nov 21, 2024
a35543c
Test
svanaeinars Nov 21, 2024
9251134
Make web proxy URL dynamic
svanaeinars Nov 21, 2024
3e5405b
chore: charts update dirty files
andes-it Nov 21, 2024
14c8be2
Fix
svanaeinars Nov 21, 2024
0964e55
chore: charts update dirty files
andes-it Nov 21, 2024
1a4eea6
Merge branch 'main' into feat/move-and-improve-e2e-tests-web
svanaeinars Nov 22, 2024
9cf35bf
Elastisearch docker-compose for offline tests
svanaeinars Nov 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ yarn nx extract-strings web

Currently, in this project, only the `Custom Page` content type utilizes the `Translation Namespace` content type for translations

## E2E Testing

### Quick Start

To run the E2E tests for the `web` app:

```bash
# Install dependencies
yarn install && yarn codegen

# Start the server
yarn nx e2e web
```

### More Resources

For further details, refer to the [E2E Testing Library README](../../libs/testing/e2e/README.md).

## Further Documentation

[Subpages](./docs/subpages.md) - Information on Layouts and Components used when
Expand Down
66 changes: 66 additions & 0 deletions apps/web/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
version: '3'
services:
elasticsearch:
image: docker.io/elasticsearch:8.8.0
ports:
- 9200:9200
- 9300:9300
environment:
- discovery.type=single-node
- xpack.security.enabled=false
healthcheck:
test: ['CMD-SHELL', 'curl -f http://localhost:9200 || exit 1']
interval: 10s
retries: 10

Comment on lines +1 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Security and configuration improvements needed for Elasticsearch service

Several important considerations for the Elasticsearch configuration:

  1. Security is completely disabled which is risky even for development
  2. No memory limits are defined which could lead to resource exhaustion
  3. No persistent volume is configured, data will be lost on container restart
  4. Version is pinned to 8.8.0 without allowing patch updates

Consider applying these improvements:

 elasticsearch:
   image: docker.io/elasticsearch:8.8.0
+  volumes:
+    - elasticsearch-data:/usr/share/elasticsearch/data
   ports:
     - 9200:9200
     - 9300:9300
+  deploy:
+    resources:
+      limits:
+        memory: 2g
   environment:
     - discovery.type=single-node
-    - xpack.security.enabled=false
+    - xpack.security.enabled=true
+    - ELASTIC_PASSWORD=changeme
+    - bootstrap.memory_lock=true
+    - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
   healthcheck:
     test: ['CMD-SHELL', 'curl -f http://localhost:9200 || exit 1']
     interval: 10s
     retries: 10

+volumes:
+  elasticsearch-data:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
version: '3'
services:
elasticsearch:
image: docker.io/elasticsearch:8.8.0
ports:
- 9200:9200
- 9300:9300
environment:
- discovery.type=single-node
- xpack.security.enabled=false
healthcheck:
test: ['CMD-SHELL', 'curl -f http://localhost:9200 || exit 1']
interval: 10s
retries: 10
version: '3'
services:
elasticsearch:
image: docker.io/elasticsearch:8.8.0
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- 9200:9200
- 9300:9300
deploy:
resources:
limits:
memory: 2g
environment:
- discovery.type=single-node
- xpack.security.enabled=true
- ELASTIC_PASSWORD=changeme
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
healthcheck:
test: ['CMD-SHELL', 'curl -f http://localhost:9200 || exit 1']
interval: 10s
retries: 10
volumes:
elasticsearch-data:

init-elasticsearch:
image: docker.io/curlimages/curl:7.85.0
depends_on:
elasticsearch:
condition: service_healthy
entrypoint: >
sh -c "
until curl -s http://elasticsearch:9200; do
echo 'Waiting for Elasticsearch...';
sleep 5;
done;
curl -X PUT 'http://elasticsearch:9200/island-is-kstnl' -H 'Content-Type: application/json' -d'
{
\"settings\": {
\"number_of_shards\": 1,
\"number_of_replicas\": 1
},
\"mappings\": {
\"properties\": {
\"title\": {
\"type\": \"text\",
\"fields\": {
\"sort\": {
\"type\": \"keyword\"
}
}
},
\"dateUpdated\": {
\"type\": \"date\"
},
\"dateCreated\": {
\"type\": \"date\"
},
\"releaseDate\": {
\"type\": \"date\"
},
\"tags\": {
\"type\": \"nested\",
\"properties\": {
\"key\": {
\"type\": \"keyword\"
},
\"type\": {
\"type\": \"keyword\"
}
}
}
}
}
}';
"
Comment on lines +27 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve Elasticsearch index configuration and mappings

The current index configuration could be enhanced:

  1. Consider using a more structured index naming pattern (e.g., including environment/version)
  2. Single replica with single shard might not be optimal for scaling
  3. Missing analyzer configuration for the title field
  4. Consider adding field validations and additional mapping properties

Consider these improvements:

-      curl -X PUT 'http://elasticsearch:9200/island-is-kstnl' -H 'Content-Type: application/json' -d'
+      curl -X PUT 'http://elasticsearch:9200/web-content-v1' -H 'Content-Type: application/json' -d'
       {
         \"settings\": {
           \"number_of_shards\": 1,
-          \"number_of_replicas\": 1
+          \"number_of_replicas\": 0,
+          \"analysis\": {
+            \"analyzer\": {
+              \"custom_analyzer\": {
+                \"type\": \"custom\",
+                \"tokenizer\": \"standard\",
+                \"filter\": [
+                  \"lowercase\",
+                  \"asciifolding\"
+                ]
+              }
+            }
+          }
         },
         \"mappings\": {
           \"properties\": {
             \"title\": {
               \"type\": \"text\",
+              \"analyzer\": \"custom_analyzer\",
               \"fields\": {
                 \"sort\": {
                   \"type\": \"keyword\"
-                }
+                },
+                \"suggest\": {
+                  \"type\": \"completion\"
+                }
               }
             },

Also, consider adding validation rules for date fields and additional mapping properties for better search functionality.

Committable suggestion skipped: line range outside the PR's diff.

214 changes: 214 additions & 0 deletions apps/web/e2e/homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import {
type BrowserContext,
createPageAndNavigate,
expect,
session,
test,
urls,
} from '@island.is/testing/e2e'

test.use({ baseURL: urls.islandisBaseUrl })

test.describe('Front page', { tag: '@fast' }, () => {
let context: BrowserContext

test.beforeAll(async ({ browser }) => {
context = await session({
browser: browser,
storageState: 'homepage.json',
homeUrl: `${urls.islandisBaseUrl}/`,
phoneNumber: '0103019',
idsLoginOn: false,
})
})

test.afterAll(async () => {
await context.close()
})

test('has expected sections @lang:is', async () => {
const page = await createPageAndNavigate(context, '/')
await expect(
page.locator('text=Öll opinber þjónusta á einum stað'),
).toBeVisible()
await expect(page.locator('data-testid=home-banner')).toBeVisible()
await expect(page.locator('data-testid=home-heading')).toBeVisible()
await expect(page.locator('data-testid=home-news')).toBeVisible()
})

test(`should have life event @lang:is`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/')
const lifeEventsCards = page.locator('[data-testid="lifeevent-card"]')
await expect(lifeEventsCards.count()).resolves.toBeGreaterThan(3)
const lifeEventHandles = await lifeEventsCards.elementHandles()
const lifeEventUrls = await Promise.all(
lifeEventHandles.map((item) => item.getAttribute('href')),
)
const lifeEventPage = await context.newPage()
for (const url of lifeEventUrls) {
if (url) {
const result = await lifeEventPage.goto(url ?? '')
await expect(
lifeEventPage.getByRole('link', { name: 'island.is logo' }),
).toBeVisible()
expect(result?.status()).toBe(200)
}
}
await lifeEventPage.close()
})

test(`should have life event @lang:en`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/en')
const lifeEventsCards = page.locator('[data-testid="lifeevent-card"]')
await expect(lifeEventsCards.count()).resolves.toBeGreaterThan(3)
const lifeEventHandles = await lifeEventsCards.elementHandles()
const lifeEventUrls = await Promise.all(
lifeEventHandles.map((item) => item.getAttribute('href')),
)
const lifeEventPage = await context.newPage()
for (const url of lifeEventUrls) {
if (url) {
const result = await lifeEventPage.goto(url ?? '')
await expect(
lifeEventPage.getByRole('link', { name: 'island.is logo' }),
).toBeVisible()
expect(result?.status()).toBe(200)
}
}
await lifeEventPage.close()
})

test(`should navigate to featured link @lang:is`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/')
const featuredLinks = page.locator('[data-testid="featured-link"]')
await expect(featuredLinks.count()).resolves.toBeGreaterThan(3)
const featuredLinksHandles = await featuredLinks.elementHandles()
const featuresLinksUrls = await Promise.all(
featuredLinksHandles.map((item) => item.getAttribute('href')),
)
const featuredPage = await context.newPage()
for (const url of featuresLinksUrls) {
if (url) {
const result = await featuredPage.goto(url)
await expect(
featuredPage.getByRole('link', { name: 'island.is logo' }),
).toBeVisible()
if (result) {
expect(result.status()).toBe(200)
}
}
}
await featuredPage.close()
})

test(`should navigate to featured link @lang:en`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/en')
const featuredLinks = page.locator('[data-testid="featured-link"]')
await expect(featuredLinks.count()).resolves.toBeGreaterThan(3)
const featuredLinksHandles = await featuredLinks.elementHandles()
const featuresLinksUrls = await Promise.all(
featuredLinksHandles.map((item) => item.getAttribute('href')),
)
const featuredPage = await context.newPage()
for (const url of featuresLinksUrls) {
if (url) {
const result = await featuredPage.goto(url)
await expect(
featuredPage.getByRole('link', { name: 'island.is logo' }),
).toBeVisible()
if (result) {
expect(result.status()).toBe(200)
}
}
}
await featuredPage.close()
})

test(`should have link on life events pages to navigate back to the main page @lang:is`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/')
const lifeEventsCards = page.locator('[data-testid="lifeevent-card"]')
const lifeEventHandles = await lifeEventsCards.elementHandles()
const lifeEventUrls = await Promise.all(
lifeEventHandles.map((item) => item.getAttribute('href')),
)
const lifeEventPage = await context.newPage()
for (const url of lifeEventUrls) {
if (url) {
await lifeEventPage.goto(url)
await lifeEventPage.locator('[data-testid="link-back-home"]').click()
await expect(
lifeEventPage.locator('data-testid=home-heading'),
).toBeVisible()
await expect(lifeEventPage).toHaveURL('/')
}
}
await lifeEventPage.close()
})

test(`should have link on life events pages to navigate back to the main page @lang:en`, async () => {
test.slow()
const page = await createPageAndNavigate(context, '/en')
const lifeEventsCards = page.locator('[data-testid="lifeevent-card"]')
const lifeEventHandles = await lifeEventsCards.elementHandles()
const lifeEventUrls = await Promise.all(
lifeEventHandles.map((item) => item.getAttribute('href')),
)
const lifeEventPage = await context.newPage()
for (const url of lifeEventUrls) {
if (url) {
await lifeEventPage.goto(url)
await lifeEventPage.locator('[data-testid="link-back-home"]').click()
await lifeEventPage.locator('[data-testid="link-back-home"]').click()
await expect(
lifeEventPage.locator('data-testid=home-heading'),
).toBeVisible()
await expect(lifeEventPage).toHaveURL('/en')
}
}
await lifeEventPage.close()
})

test('should change welcome message on language toggle @lang:is', async () => {
const page = await createPageAndNavigate(context, '/')
const homeHeading = page.locator('h1[data-testid="home-heading"]')
const icelandicHeading = await homeHeading.textContent()
await page.locator('button[data-testid="language-toggler"]:visible').click()
if (icelandicHeading) {
await expect(homeHeading).not.toHaveText(icelandicHeading)
}
await expect(page).toHaveURL('/en')
})

test('should toggle mega-menu @lang:is', async () => {
const page = await createPageAndNavigate(context, '/')
await page
.locator('[data-testid="frontpage-burger-button"]:nth-child(2)')
.click()
await expect(
page.locator('[data-testid="mega-menu-link"] > a').count(),
).resolves.toBeGreaterThan(18)
})

test('burger menu should open and close', async () => {
const page = await createPageAndNavigate(context, '/')
await page.getByRole('button', { name: 'Valmynd' }).click()

await expect(page.getByRole('dialog', { name: 'Menu' })).toBeVisible()
await expect(page.getByText('Þjónustuflokkar')).toBeVisible()
await expect(page.getByRole('dialog', { name: 'Menu' })).toBeVisible()
// Heading is "visible" behind menu
// await expect(page.getByTestId('home-heading')).not.toBeVisible()
await page
.getByRole('dialog', { name: 'Menu' })
.getByRole('button')
.getByTestId('icon-close')
.click()
await expect(page.getByTestId('home-heading')).toBeVisible()
await expect(page.getByRole('dialog', { name: 'Menu' })).not.toBeVisible()
})
})
57 changes: 57 additions & 0 deletions apps/web/e2e/search.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
type BrowserContext,
expect,
session,
test,
urls,
} from '@island.is/testing/e2e'

test.use({ baseURL: urls.islandisBaseUrl })

test.describe('Search feature', { tag: '@fast' }, () => {
let context: BrowserContext
test.beforeAll(async ({ browser }) => {
context = await session({
browser: browser,
storageState: 'homepage.json',
homeUrl: `${urls.islandisBaseUrl}/`,
phoneNumber: '0103019',
idsLoginOn: false,
})
})
test.afterAll(async () => {
await context.close()
})

test('should display search results and navigate to result page', async () => {
const testPhrase = 'umsókn'
const page = await context.newPage()
await page.goto('/', { waitUntil: 'networkidle' })
await page
.getByRole('textbox', { name: 'Leitaðu á Ísland.is' })
.fill(testPhrase)
await page.keyboard.press('Enter')
const testResults = page.locator('[data-testid="search-result"]')
await expect(testResults.count()).resolves.toBeGreaterThan(9)
const searchUrl = page.url()
await testResults.nth(0).click()
await page.waitForLoadState('networkidle')
await expect(page).not.toHaveURL(searchUrl)
})

test('should have no search results for long bogus search words', async () => {
const page = await context.newPage()
await page.goto('/', { waitUntil: 'networkidle' })
await page
.getByRole('textbox', { name: 'Leitaðu á Ísland.is' })
.fill('abcdefhijklmnopqrstuvwxyz1234567890')
await page.keyboard.press('Enter')
await page.waitForLoadState('networkidle')
const testResults = page.locator('[data-testid="search-result"]')
await expect(testResults).toHaveCount(0)
})
svanaeinars marked this conversation as resolved.
Show resolved Hide resolved

test.skip('should search in English', async () => {
return
})
})
Loading