I have this config in playwright to run different browsers for the same test
const config: PlaywrightTestConfig = {
projects: [
{
name: "Desktop Safari",
use: {
browserName: "webkit",
viewport: { width: 1200, height: 750 },
headless: false,
baseURL: BASE_URL,
httpCredentials,
},
},
// Test against mobile viewports.
{
name: "Mobile Chrome",
use: { ...devices["Pixel 5"], baseURL: BASE_URL, httpCredentials },
},
{
name: "Mobile Safari",
use: { ...devices["iPhone 12"], baseURL: BASE_URL, httpCredentials },
},
{
name: "Desktop Firefox",
use: {
browserName: "firefox",
viewport: { width: 800, height: 600 },
httpCredentials,
},
},
],
Now, I know that an element should be present IF i am on desktop but not if i am on Mobile. How do i do a check on the viewport, before expecting an element to be in there?
Something like this:
test("Test home page", async ({ page }) => {
await page.goto(BASE_URL);
const inMobile =await page.$$("text='I am mobile'");;
const inDesktop = await page.$$("I am desktop");
if (testing mobile)
expect(inMobile).toBeTruthy()
else expect(inDesktop).toBeTruthy()
});
You can use the isMobile fixture to detect if it's a mobile device. This gets set internally when using a device like you do in the projects configuration. See the following example:
test("Test home page", async ({ page, isMobile }) => {
console.log(isMobile);
});
See here for reference: https://playwright.dev/docs/api/class-fixtures#fixtures-is-mobile
Related
I'm building web app which is pwa which is cached the data to serve offline as well as it also cached the network error api requests and whenever user get online it run those apis to up to date the DB.
Application is Progressive Web App (PWA), Its built with Javascript framework Vuejs. Additional we used the workbox for recall the network failed apis functionality, these workbox functionality working on all devices except on iPhone.
I've attached the vue.config.js file which has the config of workbox, Please let me know what I can do to fix the error on iPhone.
Your help will be very appreciated!
Filename: Vue.config.js
configureWebpack: {
entry: ['babel-polyfill', './src/main.js'],
},
pwa: {
workboxPluginMode: 'GenerateSW',
workboxOptions: {
navigateFallback: '/index.html',
skipWaiting: true,
runtimeCaching: [
{
urlPattern: ^https:\/\/api\.dev\.firststep\.zinpro\.cn\/api,
handler: 'networkOnly',
options: {
cacheName: 'api',
},
},
{
^https:\/\/api\.dev\.firststep\.zinpro\.cn\/apiPattern: ^https:\/\/api\.dev\.firststep\.zinpro\.cn\/api,
method: 'POST',
handler: 'networkOnly',
options: {
backgroundSync: {
name: 'new-tasks-queue-0',
options: {
maxRetentionTime: 24 * 60, // 24 hr
},
},
},
},
{
urlPattern: ^https:\/\/api\.dev\.firststep\.zinpro\.cn\/api,
method: 'DELETE',
handler: 'networkOnly',
options: {
backgroundSync: {
name: 'delete-tasks-queue-1',
options: {
maxRetentionTime: 24 * 60, // minutes
},
},
},
},
{
urlPattern: ^https:\/\/api\.dev\.firststep\.zinpro\.cn\/api,
method: 'GET',
handler: 'networkOnly',
options: {
backgroundSync: {
name: 'new-tasks-queue-2',
options: {
maxRetentionTime: 24 * 60, // minutes
},
},
},
},
],
},
themeColor: '#000000',
iconPaths: {
favicon32: 'icons/ZinproFavicon32x32.png',
favicon16: 'icons/ZinproFavicon32x32.png',
appleTouchIcon: 'icons/Zinpro_App_Icon_192x192.png',
maskIcon: 'icons/safari-pinned-tab.svg',
msTileImage: 'icons/Zinpro_App_Icon_192x192.png',
},
},
}
I was researching about identity providers and I found supertokens (https://supertokens.com/docs/guides), it seems a nice solution but I would like to know if it also accepts LinkedIn as a third party provider because I couldn't see any info about this in docs or in any other related post. Also if you have any example code would be awesome
Find documentation about integration between supertokens and LinkedIn auth, couldn't find any.
The documentation for implementing custom providers is available here - https://supertokens.com/docs/thirdparty/common-customizations/sign-in-and-up/custom-providers. Based on that, here is a sample implementation for linkedin that you could use:
Frontend:
export const SuperTokensConfig = {
appInfo: {
appName: "SuperTokens Demo App",
apiDomain: "http://localhost:3001",
websiteDomain: "http://localhost:3000",
},
// recipeList contains all the modules that you want to
// use from SuperTokens. See the full list here: https://supertokens.com/docs/guides
recipeList: [
ThirdParty.init({
signInAndUpFeature: {
providers: [
{
id: "linkedin",
name: "Linkedin",
buttonComponent: <div style={{
cursor: "pointer",
border: "1",
paddingTop: "5px",
paddingBottom: "5px",
borderRadius: "5px",
borderStyle: "solid"
}}>Login with Linkedin</div>
}
],
},
}),
Session.init(),
],
};
Backend:
const Linkedin = (config: any): TypeProvider => {
return {
id: "linkedin",
get: (redirectURI: string | undefined, authCodeFromRequest: string | undefined, userContext: any) => {
const accessTokenParams: any = {
client_id: config.clientId,
client_secret: config.clientSecret,
grant_type: "authorization_code",
}
if (redirectURI !== undefined) accessTokenParams["redirect_uri"] = redirectURI;
if (authCodeFromRequest !== undefined) accessTokenParams["code"] = authCodeFromRequest;
const authRedirectParams: any = {
client_id: config.clientId,
scope: "r_liteprofile r_emailaddress",
response_type: "code",
}
if (redirectURI !== undefined) authRedirectParams["redirect_uri"] = redirectURI;
return {
accessTokenAPI: {
url: "https://www.linkedin.com/oauth/v2/accessToken",
params: accessTokenParams,
},
authorisationRedirect: {
url: "https://www.linkedin.com/oauth/v2/authorization",
params: authRedirectParams,
},
getClientId: () => config.clientId,
getProfileInfo: async (authCodeResponse: any, userContext: any) => {
const headers = {
Authorization: `Bearer ${authCodeResponse.access_token}`,
}
const userInfo = (await axios.get("https://api.linkedin.com/v2/me", { headers })).data
const emailInfo = (await axios.get("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))", { headers })).data
let email = ''
for (const element of emailInfo.elements) {
if (element['handle~'].emailAddress) {
email = element['handle~'].emailAddress
break
}
}
return {
id: userInfo.id,
email: {
id: email,
isVerified: false,
}
}
},
}
},
}
}
export const SuperTokensConfig: TypeInput = {
supertokens: {
connectionURI: "https://try.supertokens.com",
},
appInfo: {
appName: "SuperTokens Demo App",
apiDomain: "http://localhost:3001",
websiteDomain: "http://localhost:3000",
},
recipeList: [
ThirdParty.init({
signInAndUpFeature: {
providers: [
Linkedin({
clientId: "...",
clientSecret: "..."
}),
],
},
}),
Session.init(),
],
};
If you are using python or the golang SDK, a similar implementation in that language should work.
I have created Nuxt.js application, I decided to build in Nuxt/auth module, everything works fine in web browsers, but somethimes when user navigates with mobile browser my application is crushed, simply it don't respond nothing, also there is no api calls, but after one refresh everything works fine, I can not guess what's happening, I could not find anything in the resources available on the Internet.
const axios = require('axios')
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'app',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script: [
// { src: "//code-eu1.jivosite.com/widget/UoBOrMfSmm", async: true },
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [ '~/assets/css/transition.css', '~/assets/css/errors.css' ],
pageTransition: "fade",
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
{ src: "~/plugins/star-rating", ssr: false },
{ src: "~/plugins/mask", ssr: false },
{ src: "~/plugins/rangeSlider", ssr: false },
{ src: "~/plugins/vueSelect", ssr: false },
{ src: "~/plugins/vuelidate", ssr: false },
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
[ '#nuxtjs/google-analytics', {
id: 'xxx'
} ]
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'#nuxtjs/axios',
'#nuxtjs/toast',
'#nuxtjs/auth-next',
[ 'nuxt-lazy-load', {
defaultImage: '/spin2.gif'
} ],
[ 'nuxt-facebook-pixel-module', {
/* module options */
track: 'PageView',
pixelId: '',
autoPageView: true,
disabled: false
} ],
'nuxt-moment',
'#nuxtjs/robots',
'#nuxtjs/sitemap'
],
moment: {
locales: ['ru', 'en']
},
toast: {
position: 'top-center',
},
robots: [
{
UserAgent: '*',
Disallow: ['/user', '/admin'],
},
],
axios: {
baseURL: 'https://api.test.com/', // Used as fallback if no runtime config is provided
},
sitemap:{
exclude:[
'/user',
'/admin',
'/admin/*',
'/user/*',
],
defaults: {
changefreq: 'daily',
priority: 1,
lastmod: new Date()
},
routes: async () => {
const { data } = await axios.get('https://api.test.com/api/cars/all')
return data.map((product) => `https://test.com/product/${product.id}/${product.name}`)
}
},
loading: {
color: '#F48245',
height: '4px'
},
target: 'server',
/* auth */
auth: {
plugins:[
{ src: "~/plugins/providers", ssr:false},
],
redirect: {
login: '/',
logout: '/',
home: '/',
callback: '/callback'
},
strategies: {
local: {
token: {
property: 'user.token',
},
user: {
property: false
},
endpoints: {
login: { url: 'api/login', method: 'post' },
logout: { url: 'api/logout', method: 'post' },
user: { url: 'api/user', method: 'get' }
},
},
facebook: {
endpoints: {
userInfo: 'https://graph.facebook.com/v6.0/me?fields=id,name,picture{url}',
},
redirectUri:'xxx',
clientId: '184551510189971',
scope: ['public_profile', 'email'],
},
google: {
responseType: 'token id_token',
codeChallengeMethod: '',
clientId: 'xxx',
redirectUri: 'https://test.com/callback',
scope: ['email'],
},
},
cookie: {
prefix: 'auth.',
},
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
};
This is my plugins directory file, where i am handling client oauth process.
export default async function ({ app }) {
console.log('auth executed')
if (!app.$auth.loggedIn) {
return
} else {
console.log('auth executed inside loop')
const auth = app.$auth;
const authStrategy = auth.strategy.name;
if (authStrategy === 'facebook') {
let data2 = {
fb_token: auth.user.id,
first_name: auth.user.name
}
try {
const response = await app.$axios.$post("/api/oauth", data2);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
} else if (authStrategy === 'google') {
let dataGoogle = {
google_token: auth.user.sub,
first_name: auth.user.given_name,
last_name:auth.user.family_name
}
try {
const response = await app.$axios.$post("/api/oauth", dataGoogle);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
}
}
}
For any issues related to DOM hydration, you can check my answer here: https://stackoverflow.com/a/67978474/8816585
It does have several possible cases (dynamic content with a difference between client side and server side rendered template, some random functions, purely wrong HTML structure etc...) and also a good blog article from Alex!
so my problem is that I can't seem to make use of the "Mentions" functionality of tiptap inside a vuetify-nuxt project.
The original example can be found here
More useful info:
Example implementation from the tiptap github here
Similar question asked and not answered fully here
Similar question asked in the vuetify integration library here
To do that I'm trying to combine documentation examples.
I guess it goes wrong around the time I try to use "tippy", which is the css library used to display the popup that actually lists users to pick from (after the #), but I can't seem to understand the real issue.
So when I type # the keydown/up event listeners are functioning, but the tippy seems to not bind the popup successfully (it's not displayed), and the following error occurs:
Editor.vue?6cd8:204 Uncaught TypeError: Cannot read property '0' of undefined
at VueComponent.enterHandler (Editor.vue?6cd8:204)
at onKeyDown (Editor.vue?6cd8:175)
at Plugin.handleKeyDown (extensions.esm.js?f23d:788)
at eval (index.es.js?f904:3298)
at EditorView.someProp (index.es.js?f904:4766)
at editHandlers.keydown (index.es.js?f904:3298)
This is my tippy.js nuxt plugin:
import Vue from "vue";
import VueTippy, { TippyComponent } from "vue-tippy";
Vue.use(VueTippy, {
interactive: true,
theme: "light",
animateFill: false,
arrow: true,
arrowType: "round",
placement: "bottom",
trigger: "click",
// appendTo: () => document.getElementById("app")
});
Vue.component("tippy", TippyComponent);
This is the component in which I'm trying to show the editor and the suggestions/mentiosn functionality:
<template>
<div>
<div class="popup">
aaaa
</div>
<editor-menu-bar v-slot="{ commands }" :editor="editor">
<div class="menubar">
<v-btn class="menubar__button" #click="commands.mention({ id: 1, label: 'Fred Kühn' })">
<v-icon left>#</v-icon>
<span>Mention</span>
</v-btn>
</div>
</editor-menu-bar>
<tiptap-vuetify v-model="localValue" :extensions="extensions" :native-extensions="nativeExtensions" :toolbar-attributes="{ color: 'grey' }" #init="onInit" />
</div>
</template>
<script>
// import the component and the necessary extensions
import {
TiptapVuetify,
Heading,
Bold,
Italic,
Strike,
Underline,
Code,
CodeBlock,
Image,
Paragraph,
BulletList,
OrderedList,
ListItem,
Link,
Blockquote,
HardBreak,
HorizontalRule,
History,
} from "tiptap-vuetify";
// TESTING
import { EditorMenuBar, Editor } from "tiptap";
import { Mention } from "tiptap-extensions";
import tippy, { sticky } from "tippy.js";
export default {
components: { TiptapVuetify, EditorMenuBar },
props: {
value: {
type: String,
default: "",
},
},
data: () => ({
editor: null,
extensions: null,
nativeExtensions: null,
// TESTING
query: null,
suggestionRange: null,
filteredUsers: [],
navigatedUserIndex: 0,
insertMention: () => {},
popup: null,
}),
computed: {
localValue: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
},
},
// TESTING
hasResults() {
return this.filteredUsers.length;
},
showSuggestions() {
return this.query || this.hasResults;
},
},
created() {
this.extensions = [
History,
Blockquote,
Link,
Underline,
Strike,
Italic,
ListItem,
BulletList,
OrderedList,
[
Heading,
{
options: {
levels: [1, 2, 3],
},
},
],
Bold,
Link,
Code,
CodeBlock,
Image,
HorizontalRule,
Paragraph,
HardBreak,
];
this.nativeExtensions = [
// https://github.com/ueberdosis/tiptap/blob/main/examples/Components/Routes/Suggestions/index.vue
new Mention({
// a list of all suggested items
items: async () => {
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
return [
{ id: 1, name: "Sven Adlung" },
{ id: 2, name: "Patrick Baber" },
{ id: 3, name: "Nick Hirche" },
{ id: 4, name: "Philip Isik" },
{ id: 5, name: "Timo Isik" },
{ id: 6, name: "Philipp Kühn" },
{ id: 7, name: "Hans Pagel" },
{ id: 8, name: "Sebastian Schrama" },
];
},
// When # is pressed, we enter here
onEnter: ({ items, query, range, command, virtualNode }) => {
this.query = query; // the field that the # queries? currently empty
this.filteredUsers = items;
this.suggestionRange = range;
this.renderPopup(virtualNode); // render popup - failing
this.insertMention = command; // this is saved to be able to call it from within the popup
},
// probably when value after # is changed
onChange: ({ items, query, range, virtualNode }) => {
this.query = query;
this.filteredUsers = items;
this.suggestionRange = range;
this.navigatedUserIndex = 0;
this.renderPopup(virtualNode);
},
// mention canceled
onExit: () => {
// reset all saved values
this.query = null;
this.filteredUsers = [];
this.suggestionRange = null;
this.navigatedUserIndex = 0;
this.destroyPopup();
},
// any key down during mention typing
onKeyDown: ({ event }) => {
if (event.key === "ArrowUp") {
this.upHandler();
return true;
}
if (event.key === "ArrowDown") {
this.downHandler();
return true;
}
if (event.key === "Enter") {
this.enterHandler();
return true;
}
return false;
},
// there may be built-in filtering, not sure
onFilter: async (items, query) => {
await console.log("on filter");
},
}),
];
},
methods: {
// TESTING
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {
this.navigatedUserIndex =
(this.navigatedUserIndex + this.filteredUsers.length - 1) %
this.filteredUsers.length;
},
// navigate to the next item
// if it's the last item, navigate to the first one
downHandler() {
this.navigatedUserIndex =
(this.navigatedUserIndex + 1) % this.filteredUsers.length;
},
enterHandler() {
const user = this.filteredUsers[this.navigatedUserIndex];
if (user) {
this.selectUser(user);
}
},
// we have to replace our suggestion text with a mention
// so it's important to pass also the position of your suggestion text
selectUser(user) {
this.insertMention({
range: this.suggestionRange,
attrs: {
id: user.id,
label: user.name,
},
});
this.editor.focus();
},
renderPopup(node) {
if (this.popup) {
return;
}
// ref: https://atomiks.github.io/tippyjs/v6/all-props/
this.popup = tippy(".page", {
getReferenceClientRect: node.getBoundingClientRect, // input location
appendTo: () => document.body, // must be issue
interactive: true,
sticky: true, // make sure position of tippy is updated when content changes
plugins: [sticky],
content: this.$refs.suggestions,
trigger: "mouseenter", // manual
showOnCreate: true,
theme: "dark",
placement: "top-start",
inertia: true,
duration: [400, 200],
});
},
destroyPopup() {
if (this.popup) {
this.popup[0].destroy();
this.popup = null;
}
},
beforeDestroy() {
this.destroyPopup();
},
/**
* NOTE: destructure the editor!
*/
onInit({ editor }) {
this.editor = editor;
},
},
};
</script>
How can I get the "suggestions" item display in the aforementioned setting?
Below is my code for bottomTabs that is not working, don't worry my component names are correct, I have checked many times. The only thing is when below code loads my apps stops working , by the way i am testing it in android (tried in simulator as well as on real device ) but not working.
What may be the possible reasons? I am new to react-native
const startTabs = () => {
Navigation.setRoot({
root: {
bottomTabs: {
children: [
{
component: {
name: 'awesome-places.SharePlaceScreen',
passProps: {
text: 'This is tab 1',
myFunction: () => 'Hello from a function!',
},
},
},
{
component: {
name: 'awesome-places.FindPlaceScreen',
passProps: {
text: 'This is tab 2',
},
},
},
],
},
}
});
}