Compare commits

..
Author SHA1 Message Date
Yulia e8d57c3445 Fix flaky Access control test (#261)
Fixed flaky test by changing the way to access 
Access Control Page and add some test ids of Add Rules buttons.

Temporary removed tests for Firefox and webkit(safari)
2023-08-22 11:38:11 +02:00
pascal-fischer 0b892c0056 Add User last login and new events
Add last login to users overview list.
Additionally added new events for dashboard login, peer expiration and peer login.
2023-08-18 19:23:56 +02:00
Misha Bragin 14d9b80029 Limit UI view for the "user" role (#259) 2023-08-15 22:36:21 +02:00
16 changed files with 194 additions and 193 deletions

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
import { Page, test, expect } from "@playwright/test";
export class AccessControlPage {
private readonly accessControlUrl = 'http://localhost:3000/acls'
private readonly defaulAccessControl = this.page.getByRole('cell', { name: 'Default' })
private readonly deleteButton = this.page.getByRole('button', { name: 'Delete' })
private readonly deleteModal = this.page.getByTestId('confirm-delete-modal-title')
private readonly confirmButton = this.page.getByRole('button', { name: 'OK' })
private readonly addRulesButton = this.page.getByTestId('add-rule-empty-state-button')
constructor(private readonly page: Page) {}
async openAccessControlPage() {
await test.step('Open Access Control page', async () => {
await this.page.goto(this.accessControlUrl);
})
}
async assertDefaultAccessCotrolIsCreated() {
await test.step('Assert that default cotrol access is created', async () => {
await expect(this.defaulAccessControl).toBeVisible();
})
}
async pressDeleteButton() {
await test.step('Press delete button', async () => {
await this.deleteButton.click();
})
}
async assertDeleteModalIsVisibile() {
await test.step('Assert access control deletion modal is visible', async () => {
await expect(this.deleteModal).toBeVisible();
})
}
async pressConfirmButton() {
await test.step('Press confirm button on access control deletion modal', async () => {
await this.confirmButton.click();
})
}
async assertDefaultAccessCotrolIsDeleted() {
await test.step('Assert default access control should be deleted', async () => {
await expect(this.defaulAccessControl).not.toBeVisible();
})
}
async assertAddRuleButtonIsVisile() {
await test.step('Assert Add Rules button is visible', async () => {
await expect(this.addRulesButton).toBeVisible();
})
}
}
export default AccessControlPage;
+34
View File
@@ -0,0 +1,34 @@
import { Page, test, expect} from "@playwright/test";
export class LoginPage {
private readonly localUrl = 'http://localhost:3000/'
private readonly usernameField = this.page.getByPlaceholder('username@domain')
private readonly nextButton = this.page.getByRole('button', { name: 'next' })
private readonly passwordField = this.page.getByLabel('Password')
private readonly skipButton = this.page.getByRole('button', { name: 'skip' });
private readonly netBirdLogo = this.page.getByRole('link', { name: 'logo' })
constructor(private readonly page: Page) {}
async doLogin() {
await test.step('Login to local enviroment', async () => {
await this.page.goto(this.localUrl);
await this.usernameField.fill('admin@localhost');
await this.pressNextButton();
await this.passwordField.fill('testMe123@');
await this.pressNextButton();
if (await this.skipButton.isVisible({ timeout: 300 })) {
await this.skipButton.click();
}
await expect(this.netBirdLogo).toBeVisible();
})
}
async pressNextButton() {
await test.step('Press next button', async () => {
await this.nextButton.click();
})
}
}
export default LoginPage;
+1 -1
View File
@@ -1,4 +1,4 @@
import { Page, test, expect } from "@playwright/test";
import { Page, test } from "@playwright/test";
export class PeersPage {
private readonly addNewPeerButton = this.page.getByTestId('add-new-peer-button')
+15 -26
View File
@@ -1,33 +1,22 @@
import { test, expect } from '@playwright/test';
import {AddPeerModal} from '../pages/modals/add-peer-modal'
import {TopMenu} from '../pages/top-menu';
import { test } from '@playwright/test'
import {LoginPage} from '../pages/login-page'
import {AccessControlPage} from '../pages/access-control-page'
const URL = 'https://app.netbird.io/'
const localUrl = 'http://localhost:3000/'
let addPeerModal: AddPeerModal;
let topMenu: TopMenu;
let loginPage: LoginPage
let accessControlPage: AccessControlPage
test.beforeEach(async ({ page }) => {
addPeerModal = new AddPeerModal(page);
await page.goto(localUrl);
await page.getByPlaceholder('username@domain').fill('admin@localhost');
await page.getByRole('button', { name: 'next' }).click();
await page.getByLabel('Password').fill('testMe123@');
await page.getByRole('button', { name: 'next' }).click();
const skipButton = page.getByRole('button', { name: 'skip' });
if (await skipButton.isVisible({ timeout: 300 })) {
await skipButton.click();
}
await addPeerModal.assertPeerModalIsVisible();
loginPage = new LoginPage(page);
await loginPage.doLogin();
});
test('Confirm that new user has Default access', async ({ page }) => {
topMenu = new TopMenu(page);
await addPeerModal.closeAddPeerModal();
await addPeerModal.assertPeerModalIsNotVisible();
await topMenu.clickOnAccessControlOnTopMenu();
await expect(page.getByRole('cell', { name: 'Default' })).toBeVisible();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByTestId('confirm-delete-modal-title')).toBeVisible();
await page.getByRole('button', { name: 'Cancel' }).click();
accessControlPage = new AccessControlPage(page);
await accessControlPage.openAccessControlPage();
await accessControlPage.assertDefaultAccessCotrolIsCreated();
await accessControlPage.pressDeleteButton();
await accessControlPage.assertDeleteModalIsVisibile();
await accessControlPage.pressConfirmButton();
await accessControlPage.assertDefaultAccessCotrolIsDeleted();
await accessControlPage.assertAddRuleButtonIsVisile();
});
+12 -19
View File
@@ -1,48 +1,41 @@
import { test } from '@playwright/test';
import { test } from '@playwright/test'
import {AddPeerModal} from '../pages/modals/add-peer-modal'
import {PeersPage} from '../pages/peers-page'
import {LoginPage} from '../pages/login-page'
const URL = 'https://app.netbird.io/'
const localUrl = 'http://localhost:3000/'
let addPeerModal: AddPeerModal;
let peersPage: PeersPage;
let addPeerModal: AddPeerModal
let peersPage: PeersPage
let loginPage: LoginPage
test.beforeEach(async ({ page }) => {
addPeerModal = new AddPeerModal(page);
await page.goto(localUrl);
await page.getByPlaceholder('username@domain').fill('admin@localhost');
await page.getByRole('button', { name: 'next' }).click();
await page.getByLabel('Password').fill('testMe123@');
await page.getByRole('button', { name: 'next' }).click();
const skipButton = page.getByRole('button', { name: 'skip' });
if (await skipButton.isVisible({ timeout: 300 })) {
await skipButton.click();
}
loginPage = new LoginPage(page);
await loginPage.doLogin();
await addPeerModal.assertPeerModalIsVisible();
});
test('Test Linux tab on a first access add peer modal / @bc', async function ({ }) {
test('Test Linux tab on a first access add peer modal / @bc', async function () {
await addPeerModal.openLinuxTab();
await addPeerModal.assertLinuxTabHasCorrectText();
});
test('Test Windows tab on a first access add peer modal / @bc', async ({ }) => {
test('Test Windows tab on a first access add peer modal / @bc', async () => {
await addPeerModal.openWindowsTab();
await addPeerModal.assertWindowsDownloadButtonHasCorrectLink();
});
test('Test MacOS tab on a first access add peer modal / @bc', async ({ }) => {
test('Test MacOS tab on a first access add peer modal / @bc', async () => {
await addPeerModal.openMacTab();
await addPeerModal.assertIntelDownloadButtonHasCorrectLink();
await addPeerModal.assertM1M2DownloadButtonHasCorrectLink();
});
test('Test Android tab on a first access add peer modal', async ({ }) => {
test('Test Android tab on a first access add peer modal', async () => {
await addPeerModal.openAndroidTab();
await addPeerModal.assertAndroidDownloadButtonHasCorrectLink();
});
test('Test Docker tab on a first access add peer modal', async ({ }) => {
test('Test Docker tab on a first access add peer modal', async () => {
await addPeerModal.openDockerTab();
await addPeerModal.assertDockerDownloadButtonHasCorrectLink();
});
-33
View File
@@ -12,8 +12,6 @@
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@stripe/react-stripe-js": "^2.1.2",
"@stripe/stripe-js": "^2.1.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
@@ -3407,24 +3405,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
"node_modules/@stripe/react-stripe-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz",
"integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.44.1 || ^2.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.0.tgz",
"integrity": "sha512-AdMHWqIq/s3TLmoyuzdb6l21STKDKNm82cjce1VelyQGinVNjVgSEPH5UDtxENEP7OzgMZH8RTphFMi0fMJGLQ=="
},
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -19497,19 +19477,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
"@stripe/react-stripe-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz",
"integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@stripe/stripe-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.0.tgz",
"integrity": "sha512-AdMHWqIq/s3TLmoyuzdb6l21STKDKNm82cjce1VelyQGinVNjVgSEPH5UDtxENEP7OzgMZH8RTphFMi0fMJGLQ=="
},
"@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
+2 -4
View File
@@ -7,8 +7,6 @@
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@stripe/react-stripe-js": "^2.1.2",
"@stripe/stripe-js": "^2.1.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
@@ -81,7 +79,7 @@
]
},
"devDependencies": {
"@playwright/test": "^1.36.2",
"@types/react-syntax-highlighter": "^15.5.3"
"@types/react-syntax-highlighter": "^15.5.3",
"@playwright/test": "^1.36.2"
}
}
+9 -9
View File
@@ -39,15 +39,15 @@ export default defineConfig({
use: { channel: 'chrome', },
},
{
name: 'firefox',
use: { browserName: 'firefox', },
},
{
name: 'webkit',
use: { browserName: 'webkit', },
},
// {
// name: 'firefox',
// use: { browserName: 'firefox', },
// },
//
// {
// name: 'webkit',
// use: { browserName: 'webkit', },
// },
/* Test against mobile viewports. */
// {
View File
Whitespace-only changes.
+1
View File
@@ -8,6 +8,7 @@ export interface User {
is_current?: boolean;
is_service_user?: boolean;
is_blocked?: boolean;
last_login?: string;
}
export interface FormUser extends User {
+3 -1
View File
@@ -100,7 +100,7 @@ export const timeAgo = (dateParam) => {
const isToday = today.toDateString() === date.toDateString();
const isYesterday = yesterday.toDateString() === date.toDateString();
const isThisYear = today.getFullYear() === date.getFullYear();
const never = date.getFullYear() === 1;
if (seconds < -1) {
return getFormattedDate(date, false, true);
@@ -118,6 +118,8 @@ export const timeAgo = (dateParam) => {
return getFormattedDate(date, 'yesterday'); // Yesterday at 10:20
} else if (isThisYear) {
return getFormattedDate(date, false, true); // 10. January at 10:20
} else if (never) {
return 'never';
}
return getFormattedDate(date); // 10. January 2017. at 10:20
+2
View File
@@ -666,6 +666,7 @@ export const AccessControl = () => {
<Row justify="end">
<Col>
<Button
data-testid="add-rule-main-button"
type="primary"
disabled={savedPolicy.loading}
onClick={onClickAddNewPolicy}
@@ -719,6 +720,7 @@ export const AccessControl = () => {
</a>
</Paragraph>
<Button
data-testid="add-rule-empty-state-button"
size={"middle"}
type="primary"
onClick={() => onClickAddNewPolicy()}
+18
View File
@@ -170,6 +170,21 @@ export const Activity = () => {
let body = <></>;
const user = users?.find((u) => u.id === event.initiator_id);
switch (event.activity_code) {
case "peer.login.expire":
body = (
<span
style={{
height: "auto",
whiteSpace: "normal",
textAlign: "left",
}}
>
<Row>
<Text type="secondary">System</Text>
</Row>
</span>
);
break;
case "setupkey.peer.add":
const key = setupKeys?.find((k) => k.id === event.initiator_id);
if (key) {
@@ -255,6 +270,7 @@ export const Activity = () => {
switch (event.activity_code) {
case "account.create":
case "user.join":
case "dashboard.login":
return "-";
case "rule.add":
case "rule.delete":
@@ -290,6 +306,8 @@ export const Activity = () => {
case "peer.rename":
case "peer.login.expiration.disable":
case "peer.login.expiration.enable":
case "user.peer.login":
case "peer.login.expire":
return renderMultiRowSpan(event.meta.fqdn, event.meta.ip);
case "route.add":
case "route.delete":
+5 -5
View File
@@ -633,16 +633,16 @@ export const Peers = () => {
<Container style={{ paddingTop: "40px" }}>
<Row>
<Col span={24}>
<Title className="page-heading">Peers</Title>
<Title className="page-heading">{isAdmin ? "Peers" : "My peers"}</Title>
{peers.length ? (
<Paragraph style={{ marginTop: "5px" }}>
A list of all machines and devices connected to your private
network. Use this view to manage peers
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
"A list of all your machines and devices that you connected to NetBird."}
</Paragraph>
) : (
<Paragraph style={{ marginTop: "5px" }} type={"secondary"}>
A list of all machines and devices connected to your private
network. Use this view to manage peers
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
"A list of all your machines and devices that you connected to NetBird."}
</Paragraph>
)}
+35 -26
View File
@@ -31,7 +31,7 @@ import { useGetTokenSilently } from "../utils/token";
import { actions as groupActions } from "../store/group";
import { Group } from "../store/group/types";
import { TooltipPlacement } from "antd/es/tooltip";
import { capitalize, isLocalDev, isNetBirdHosted } from "../utils/common";
import {capitalize, isLocalDev, isNetBirdHosted, timeAgo} from "../utils/common";
import { usePageSizeHelpers } from "../utils/pageSize";
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
import InviteUserPopup from "../components/popups/InviteUserPopup";
@@ -499,45 +499,54 @@ export const RegularUsers = () => {
defaultSortOrder="ascend"
render={(text, record, index) => {
const btn = (
<Button
type="text"
onClick={() =>
handleEditUser(record as UserDataTable)
}
className="tooltip-label"
>
<Text className="font-500">
{text && text.trim() !== ""
? text
: (record as User).id}
</Text>
</Button>
<Button
type="text"
onClick={() =>
handleEditUser(record as UserDataTable)
}
className="tooltip-label"
>
<Text className="font-500">
{text && text.trim() !== ""
? text
: (record as User).id}
</Text>
</Button>
);
if ((record as User).is_current) {
return (
<div>
{btn}
<Tag color="blue">me</Tag>
</div>
<>
<div>
{btn}
<Tag color="blue">me</Tag>
</div>
<Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
</>
);
}
if ((record as User).status === "invited") {
return (
<div>
{btn}
<Tag color="gold">invited</Tag>
</div>
<>
<div>
{btn}
<Tag color="gold">invited</Tag>
</div>
<Text type={"secondary"} style={{paddingLeft: "15px", paddingTop: "0px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
</>
);
}
if ((record as User).status === "blocked") {
return (
<div>
{btn}
<Tag color="red">blocked</Tag>
</div>
<>
<div>
{btn}
<Tag color="red">blocked</Tag>
</div>
<Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
</>
);
}
+1 -69
View File
@@ -33,7 +33,6 @@ import ExpiresInInput, {
expiresInToSeconds,
secondsToExpiresIn,
} from "./ExpiresInInput";
// import CheckoutPage from "../components/CheckoutForm";
import Column from "antd/lib/table/Column";
import TableSpin from "../components/Spin";
import { checkExpiresIn } from "../utils/common";
@@ -56,8 +55,6 @@ const { Title, Paragraph, Text } = Typography;
const styleNotification = { marginTop: 85 };
export const Settings = () => {
const [paymentCompleted, setPaymentCompleted] = useState(false);
const { getTokenSilently } = useGetTokenSilently();
const dispatch = useDispatch();
const { pageSize, onChangePageSize, pageSizeOptions } = usePageSizeHelpers(
@@ -574,11 +571,7 @@ export const Settings = () => {
"System settings",
"sub2",
<SettingOutlined />,
[
getItem("Authentication", "auth"),
getItem("Groups", "groups"),
getItem("Billing", "billing"),
],
[getItem("Authentication", "auth"), getItem("Groups", "groups")],
"group"
),
];
@@ -1129,67 +1122,6 @@ export const Settings = () => {
</Col>
</Row>
)}
{billingClicked && (
<Row>
<Col span={24}>
<Space
direction="vertical"
size="large"
style={{ display: "flex" }}
>
<Row style={{ width: "100%" }}>
<Col span={24}>
<Card loading={loading}>
<div
style={{
color: "rgba(0, 0, 0, 0.88)",
fontWeight: "500",
fontSize: "18px",
marginBottom: "20px",
}}
>
Billing - Free plan
</div>
<Row>
<Col span={12} style={{ marginBottom: "20px" }}>
<Alert
description="The Free plan allows you to add up to 5 users and 100 peers."
type="info"
action={
<Space>
<Button size="small" type="primary">
Upgrade
</Button>
</Space>
}
/>
</Col>
<Space size={30} wrap></Space>
<Col span={24}>
<Progress
strokeColor={"#1890FF"}
percent={60}
size={[200, 20]}
format={(percent) => `${users.length}/5 users`}
/>
</Col>
<Col span={24}>
<Progress
strokeColor={"#1890FF"}
percent={10}
size={[500, 20]}
format={(percent) => `10/100 peers`}
/>
</Col>
{/* <CheckoutPage /> */}
</Row>
</Card>
</Col>
</Row>
</Space>
</Col>
</Row>
)}
</Col>
</Row>
</Container>