puppeteer#Browser TypeScript Examples

The following examples show how to use puppeteer#Browser. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: puppeteerCtx.ts    From mockiavelli with MIT License 7 votes vote down vote up
export function setupPuppeteerCtx() {
    let browser: Browser;

    const testCtx: PuppeteerTestCtx = {};

    beforeAll(async () => {
        browser = await launch({
            headless: true,
            devtools: false,
            args: ['--no-sandbox'],
        });
    });

    afterAll(async () => {
        await browser.close();
    });

    beforeEach(async () => {
        // Setup new page (tab)
        testCtx.page = await browser.newPage();
        await testCtx.page.goto(`http://localhost:${PORT}`);

        // Instantiate
        testCtx.mockiavelli = await Mockiavelli.setup(testCtx.page);
        testCtx.makeRequest = makeRequestFactory(testCtx.page);
    });

    afterEach(async () => {
        await testCtx.page.close();
    });

    return testCtx;
}
Example #2
Source File: utils.ts    From cli with Apache License 2.0 7 votes vote down vote up
async function clearChromeBrowsingData(browser: Browser) {
  const pages = await browser.pages();

  const pageClearDataPromise = [];
  for (const page of pages) {
    const client = await page.target().createCDPSession();
    pageClearDataPromise.push(client.send('Network.clearBrowserCookies'));
    pageClearDataPromise.push(client.send('Network.clearBrowserCache'));
  }
  return Promise.all(pageClearDataPromise);
}
Example #3
Source File: browser.ts    From cli with Apache License 2.0 6 votes vote down vote up
export async function captureScreenshots(
  browser: Browser,
  screenshotName?: string
): Promise<void> {
  let pageCount = 0;
  for (const page of await browser.pages()) {
    page.url();
    try {
      await page.screenshot({
        fullPage: true,
        type: 'png',
        path: resolve(
          SCREENSHOTS_PATH,
          (screenshotName ??
            expect.getState().currentTestName.trim().replace(/\W/g, '_')) +
            `-${pageCount++}.png`
        ),
      });
    } catch (error) {
      console.warn('Failed to record screenshot.');
      console.warn(error);
    }
  }
}
Example #4
Source File: login.ts    From cli with Apache License 2.0 6 votes vote down vote up
async function getLoginPage(browser: Browser) {
  const page = (await browser.pages()).find(isLoginPage);
  if (page) {
    return page;
  }
  return new Promise<Page>((resolve) => {
    browser.on('targetchanged', async (target: Target) => {
      const page = await target.page();
      if (page && isLoginPage(page)) {
        resolve(page);
      }
    });
  });
}
Example #5
Source File: browser.ts    From cli with Apache License 2.0 6 votes vote down vote up
/**
 * Closes all pages of the targeted browser instance.
 * @param browser targeted browser instance.
 */
export async function closeAllPages(browser: Browser) {
  const pages = await browser.pages();
  const pageClosePromises: Promise<void>[] = [];
  for (const page of pages) {
    pageClosePromises.push(page.close());
  }
  return pageClosePromises;
}
Example #6
Source File: login.ts    From cli with Apache License 2.0 6 votes vote down vote up
export async function loginWithOffice(browser: Browser) {
  const {ORG_ID: orgId, PLATFORM_ENV: env} = process.env;
  if (!orgId) {
    throw new Error('Missing organization ID');
  }
  if (!env) {
    throw new Error('Missing platform environment');
  }
  if (await isLoggedin()) {
    return;
  }

  const loginProcess = runLoginCommand(orgId, env);

  await startLoginFlow(browser);
  return loginProcess;
}
Example #7
Source File: index.ts    From pdf-generator-service with MIT License 6 votes vote down vote up
createApp().then(({ app, container }) => {
  const logger = container.resolve<Consola>('logger')

  function handleTearDown() {
    app.set('gracefullyExiting', true)
    logger.info('Attempting gracefully shutdown of the server, waiting for remaining connections to complete.')

    server.close(async () => {
      logger.info('No more connections, shutting down.')
      const browser = container.resolve<Browser>('browser')
      await browser.close()
      process.exit()
    })

    setTimeout(() => {
      logger.error('Could not close connections in time, forcefully shutting down.')
      process.exit(1)
    }, 30 * 100) // 30s
  }

  process.on('SIGINT', handleTearDown)
  process.on('SIGTERM', handleTearDown)

  const server = app.listen(port, () => {
    logger.success(`${pkg.name} v${pkg.version} is running at http://localhost:${port}`)
  })
})
Example #8
Source File: sessions.ts    From FlareSolverr with MIT License 6 votes vote down vote up
export async function create(session: string, options: SessionCreateOptions): Promise<SessionsCacheItem> {
  log.debug('Creating new session...')

  const sessionId = session || UUIDv1()

  // NOTE: cookies can't be set in the session, you need to open the page first

  const puppeteerOptions: any = {
    product: 'firefox',
    headless: process.env.HEADLESS !== 'false',
    timeout: Number(process.env.BROWSER_TIMEOUT) || 40000
  }

  puppeteerOptions.extraPrefsFirefox = buildExtraPrefsFirefox(options.proxy)

  // if we are running inside executable binary, change browser path
  if (typeof (process as any).pkg !== 'undefined') {
    const exe = process.platform === "win32" ? 'firefox.exe' : 'firefox';
    puppeteerOptions.executablePath = path.join(path.dirname(process.execPath), 'firefox', exe)
  }

  log.debug('Launching web browser...')
  let browser: Browser = await puppeteer.launch(puppeteerOptions)
  if (!browser) {
    throw Error(`Failed to launch web browser.`)
  }

  sessionCache[sessionId] = {
    sessionId: sessionId,
    browser: browser
  }

  return sessionCache[sessionId]
}
Example #9
Source File: basePath.spec.ts    From next-rpc with MIT License 6 votes vote down vote up
describe('basic-app', () => {
  let browser: Browser;
  let app: RunningNextApp;

  beforeAll(async () => {
    await Promise.all([
      buildNext(FIXTURE_PATH),
      puppeteer.launch().then((b) => (browser = b)),
    ]);
    app = await startNext(FIXTURE_PATH);
  }, 30000);

  afterAll(async () => {
    await Promise.all([browser && browser.close(), app && app.kill()]);
  });

  test('should call the rpc method on a basePath', async () => {
    const page = await browser.newPage();
    try {
      await page.goto(new URL('/hello/world', app.url).toString());
      const ssrData = await page.$eval('#ssr', (el) => el.textContent);
      expect(ssrData).toBe('foo bar');
      await page.waitForSelector('#browser');
      const browserData = await page.$eval('#browser', (el) => el.textContent);
      expect(browserData).toBe('baz quux');
    } finally {
      await page.close();
    }
  });
});
Example #10
Source File: app.module.ts    From nest-puppeteer with MIT License 5 votes vote down vote up
constructor(@InjectBrowser() private readonly browser: Browser) {}
Example #11
Source File: Pdf.ts    From pdf-generator-service with MIT License 5 votes vote down vote up
private browser: Browser
Example #12
Source File: Pdf.ts    From pdf-generator-service with MIT License 5 votes vote down vote up
constructor(browser: Browser) {
    this.browser = browser
  }
Example #13
Source File: puppeteer-core.module.ts    From nest-puppeteer with MIT License 5 votes vote down vote up
static forRoot(
    launchOptions: LaunchOptions = DEFAULT_CHROME_LAUNCH_OPTIONS,
    instanceName: string = DEFAULT_PUPPETEER_INSTANCE_NAME,
  ): DynamicModule {
    const instanceNameProvider = {
      provide: PUPPETEER_INSTANCE_NAME,
      useValue: instanceName,
    };

    const browserProvider = {
      provide: getBrowserToken(instanceName),
      async useFactory() {
        return await launch(launchOptions);
      },
    };

    const contextProvider = {
      provide: getContextToken(instanceName),
      async useFactory(browser: Browser) {
        return browser.createIncognitoBrowserContext();
      },
      inject: [getBrowserToken(instanceName)],
    };

    const pageProvider = {
      provide: getPageToken(instanceName),
      async useFactory(context: BrowserContext) {
        return await context.newPage();
      },
      inject: [getContextToken(instanceName)],
    };

    return {
      module: PuppeteerCoreModule,
      providers: [
        instanceNameProvider,
        browserProvider,
        contextProvider,
        pageProvider,
      ],
      exports: [browserProvider, contextProvider, pageProvider],
    };
  }
Example #14
Source File: routes.spec.ts    From pdf-generator-service with MIT License 5 votes vote down vote up
afterAll(async () => {
  await instance?.container.resolve<Browser>('browser').close()
})
Example #15
Source File: puppeteer-core.module.ts    From nest-puppeteer with MIT License 5 votes vote down vote up
static forRootAsync(options: PuppeteerModuleAsyncOptions): DynamicModule {
    const puppeteerInstanceName =
      options.instanceName ?? DEFAULT_PUPPETEER_INSTANCE_NAME;

    const instanceNameProvider = {
      provide: PUPPETEER_INSTANCE_NAME,
      useValue: puppeteerInstanceName,
    };

    const browserProvider = {
      provide: getBrowserToken(puppeteerInstanceName),
      async useFactory(puppeteerModuleOptions: PuppeteerModuleOptions) {
        return await launch(
          puppeteerModuleOptions.launchOptions ?? DEFAULT_CHROME_LAUNCH_OPTIONS,
        );
      },
      inject: [PUPPETEER_MODULE_OPTIONS],
    };

    const contextProvider = {
      provide: getContextToken(puppeteerInstanceName),
      async useFactory(browser: Browser) {
        return await browser.createIncognitoBrowserContext();
      },
      inject: [
        PUPPETEER_MODULE_OPTIONS,
        getBrowserToken(puppeteerInstanceName),
      ],
    };

    const pageProvider = {
      provide: getPageToken(puppeteerInstanceName),
      async useFactory(context: BrowserContext) {
        return await context.newPage();
      },
      inject: [
        PUPPETEER_MODULE_OPTIONS,
        getContextToken(puppeteerInstanceName),
      ],
    };

    const asyncProviders = this.createAsyncProviders(options);

    return {
      module: PuppeteerCoreModule,
      imports: options.imports,
      providers: [
        ...asyncProviders,
        browserProvider,
        contextProvider,
        pageProvider,
        instanceNameProvider,
      ],
      exports: [browserProvider, contextProvider, pageProvider],
    };
  }
Example #16
Source File: login.ts    From cli with Apache License 2.0 5 votes vote down vote up
async function startLoginFlow(browser: Browser) {
  const username = process.env.PLATFORM_USER_NAME;
  const password = process.env.PLATFORM_USER_PASSWORD;

  if (!username || !password) {
    throw new Error('Missing login credentials');
  }

  const page = await getLoginPage(browser);

  await page.waitForSelector(LoginSelectors.loginWithOfficeButton, {
    visible: true,
  });

  await Promise.all([
    page.click(LoginSelectors.loginWithOfficeButton),
    page.waitForNavigation({waitUntil: 'networkidle2'}),
    page.waitForSelector(LoginSelectors.emailView),
  ]);

  await page.waitForSelector(LoginSelectors.emailInput, {
    visible: true,
  });
  await page.type(LoginSelectors.emailInput, username);
  await page.waitForSelector(LoginSelectors.SubmitInput, {
    visible: true,
  });
  await Promise.all([
    page.click(LoginSelectors.SubmitInput),
    page.waitForSelector(LoginSelectors.passwordView),
  ]);

  await page.waitForSelector(LoginSelectors.passwordInput, {
    visible: true,
  });
  await page.type(LoginSelectors.passwordInput, password);
  await page.waitForSelector(LoginSelectors.SubmitInput, {
    visible: true,
  });
  await Promise.all([
    page.click(`${LoginSelectors.passwordView} ${LoginSelectors.SubmitInput}`),
    page.waitForNavigation({waitUntil: 'networkidle2', timeout: 2 * 60e3}),
  ]);

  await staySignedIn(page);

  await possiblyAcceptCustomerAgreement(page);

  await retry(async () => strictEqual(await isLoggedin(), true));

  if ((await browser.pages()).length < 2) {
    await browser.newPage();
  }

  await page.close();
}
Example #17
Source File: puppeteer-core.module.ts    From nest-puppeteer with MIT License 5 votes vote down vote up
async onModuleDestroy() {
    const browser: Browser = this.moduleRef.get(
      getBrowserToken(this.instanceName),
    );

    if (browser?.isConnected()) await browser.close();
  }
Example #18
Source File: PuppeteerRunnerFactory.ts    From double-agent with MIT License 5 votes vote down vote up
browser?: Browser;
Example #19
Source File: browser.ts    From cli with Apache License 2.0 5 votes vote down vote up
export async function openNewPage(browser: Browser, page?: Page | undefined) {
  const newPage = await browser.newPage();
  if (page) {
    await page.close();
  }
  return newPage;
}
Example #20
Source File: browser.ts    From cli with Apache License 2.0 5 votes vote down vote up
export async function getNewBrowser(): Promise<Browser> {
  return await puppeteer.launch({
    headless: true,
    args: getChromeDefaultOptions(),
  });
}
Example #21
Source File: browser.ts    From cli with Apache License 2.0 5 votes vote down vote up
/**
 * Return the browser instance.
 */
export async function connectToChromeBrowser(): Promise<Browser> {
  const wsURL = await getWsUrl();
  return puppeteer.connect({browserWSEndpoint: wsURL});
}
Example #22
Source File: auth.specs.ci.ts    From cli with Apache License 2.0 5 votes vote down vote up
describe('auth', () => {
  describe('login', () => {
    const {
      ORG_ID: testOrg,
      PLATFORM_ENV: platformEnv,
      PLATFORM_HOST: platformHost,
    } = process.env;
    let browser: Browser;
    let chrome: LaunchedChrome;
    let processManager: ProcessManager;

    beforeAll(async () => {
      chrome = await launchChrome({
        port: 9222,
        userDataDir: false,
        maxConnectionRetries: 240, //equivalent to 2 minutes with the default pollrate of 500ms
      });

      browser = await connectToChromeBrowser();
      processManager = new ProcessManager();
    }, 5 * 60e3);

    afterAll(async () => {
      await chrome.kill();
    });

    afterEach(async () => {
      await captureScreenshots(browser);
      await processManager.killAllProcesses();
    }, 5e3);

    it('should open the platform page', async () => {
      const args: string[] = [
        'node',
        process.env.CLI_EXEC_PATH!,
        'auth:login',
        `-e=${platformEnv}`,
        `-o=${testOrg}`,
      ];
      await captureScreenshots(browser);
      const cliTerminal = new Terminal(
        args.shift()!,
        args,
        undefined,
        processManager,
        'auth-login'
      );

      cliTerminal
        .when(isGenericYesNoPrompt)
        .on('stderr')
        .do(answerPrompt('n'))
        .once();

      await retry(async () => {
        const pages = await browser.pages();
        const loginUrl = new URL('/login', platformHost);
        expect(pages.some((page) => page.url() === loginUrl.href)).toBeTruthy();
      });
    }, 30e3);
  });
});
Example #23
Source File: base.ts    From epicgames-freegames-node with MIT License 5 votes vote down vote up
protected browser: Browser;
Example #24
Source File: puppeteer.ts    From epicgames-freegames-node with MIT License 5 votes vote down vote up
safeLaunchBrowser = (L: Logger): Promise<Browser> => {
  L.debug('Launching a new browser');
  return retryFunction(() => puppeteer.launch(launchArgs), L, 'browser launch');
}
Example #25
Source File: puppeteer.ts    From epicgames-freegames-node with MIT License 5 votes vote down vote up
safeNewPage = async (browser: Browser, L: Logger): Promise<Page> => {
  L.debug('Launching a new page');
  const page = await retryFunction(() => browser.newPage(), L, 'new page');
  page.setDefaultTimeout(config.browserNavigationTimeout);
  return page;
}
Example #26
Source File: routes.ts    From CloudProxy with MIT License 5 votes vote down vote up
async function setupPage(ctx: RequestContext, params: BaseRequestAPICall, browser: Browser): Promise<Page> {
  const page = await browser.newPage()

  // merge session defaults with params
  const { method, postData, userAgent, headers, cookies, proxy } = params

  let overrideResolvers: OverrideResolvers = {}

  if (method !== 'GET') {
    log.debug(`Setting method to ${method}`)
    overrideResolvers.method = request => method
  }

  if (postData) {
    log.debug(`Setting body data to ${postData}`)
    overrideResolvers.postData = request => postData
  }

  if (userAgent) {
    log.debug(`Using custom UA: ${userAgent}`)
    await page.setUserAgent(userAgent)
  }

  if (headers) {
    log.debug(`Adding custom headers: ${JSON.stringify(headers, null, 2)}`,)
    overrideResolvers.headers = request => Object.assign(request.headers(), headers)
  }

  if (cookies) {
    log.debug(`Setting custom cookies: ${JSON.stringify(cookies, null, 2)}`,)
    await page.setCookie(...cookies)
  }

  // if any keys have been set on the object
  if (Object.keys(overrideResolvers).length > 0) {
    log.debug(overrideResolvers)
    let callbackRunOnce = false
    const callback = (request: Request) => {

      if (callbackRunOnce || !request.isNavigationRequest()) {
        request.continue()
        return
      }

      callbackRunOnce = true
      const overrides: Overrides = {}

      Object.keys(overrideResolvers).forEach((key: OverridesProps) => {
        // @ts-ignore
        overrides[key] = overrideResolvers[key](request)
      });

      log.debug(overrides)

      request.continue(overrides)
    }

    await page.setRequestInterception(true)
    page.on('request', callback)
  }

  return page
}
Example #27
Source File: vue.specs.ts    From cli with Apache License 2.0 4 votes vote down vote up
describe('ui:create:vue', () => {
  let browser: Browser;
  const processManagers: ProcessManager[] = [];
  let page: Page;
  const oldEnv = process.env;
  const parentDir = 'vue';
  const projectName = `${process.env.TEST_RUN_ID}-${parentDir}-project`;
  const projectPath = join(getUIProjectPath(), parentDir, projectName);
  let clientPort: number;
  let serverPort: number;

  const searchPageEndpoint = () => `http://localhost:${clientPort}`;

  const tokenServerEndpoint = () => `http://localhost:${serverPort}/token`;

  const forceApplicationPorts = (clientPort: number, serverPort: number) => {
    const envPath = getPathToEnvFile(projectPath);
    const environment = parse(readFileSync(envPath, {encoding: 'utf-8'}));

    const updatedEnvironment = {
      ...environment,
      PORT: clientPort,
      VUE_APP_SERVER_PORT: serverPort,
    };
    truncateSync(envPath);
    for (const [key, value] of Object.entries(updatedEnvironment)) {
      appendFileSync(envPath, `${key}=${value}${EOL}`);
    }
  };

  const waitForAppRunning = (appTerminal: Terminal) =>
    appTerminal
      .when(/App running at:/)
      .on('stdout')
      .do()
      .once();

  const getAllocatedPorts = () => {
    const envVariables = parse(
      readFileSync(getPathToEnvFile(projectPath), {encoding: 'utf-8'})
    );

    if (!envVariables) {
      throw new Error('Unable to load project environment variables');
    }

    clientPort = parseInt(envVariables.PORT);
    serverPort = parseInt(envVariables.VUE_APP_SERVER_PORT);

    return [clientPort, serverPort];
  };

  const buildApplication = async (processManager: ProcessManager) => {
    const buildTerminal = await setupUIProject(
      processManager,
      'ui:create:vue',
      projectName,
      {projectDir: projectPath}
    );

    const buildTerminalExitPromise = Promise.race([
      buildTerminal.when('exit').on('process').do().once(),
      buildTerminal
        .when(/Happy hacking!/)
        .on('stdout')
        .do()
        .once(),
    ]);

    await buildTerminal
      .when(isGenericYesNoPrompt)
      .on('stdout')
      .do(answerPrompt(`y${EOL}`))
      .until(buildTerminalExitPromise);

    await buildTerminalExitPromise;
  };

  const startApplication = async (
    processManager: ProcessManager,
    debugName = 'vue-server'
  ) => {
    const args = [...npm(), 'run', 'start'];

    const serverTerminal = new Terminal(
      args.shift()!,
      args,
      {
        cwd: projectPath,
      },
      processManager,
      debugName
    );
    return serverTerminal;
  };

  beforeAll(async () => {
    await loginWithApiKey(
      process.env.PLATFORM_API_KEY!,
      process.env.ORG_ID!,
      process.env.PLATFORM_ENV!
    );
    const buildProcessManager = new ProcessManager();
    processManagers.push(buildProcessManager);
    browser = await getNewBrowser();
    await buildApplication(buildProcessManager);
    await buildProcessManager.killAllProcesses();
  }, 15 * 60e3);

  beforeEach(async () => {
    jest.resetModules();
    process.env = {...oldEnv};
    page = await openNewPage(browser, page);
  });

  afterEach(async () => {
    await captureScreenshots(browser);
  });

  afterAll(async () => {
    process.env = oldEnv;
    await browser.close();
    await Promise.all(
      processManagers.map((manager) => manager.killAllProcesses())
    );
  });

  describe('when the project is configured correctly', () => {
    let serverProcessManager: ProcessManager;
    let interceptedRequests: HTTPRequest[] = [];
    let consoleInterceptor: BrowserConsoleInterceptor;
    const searchboxSelector = '#search-page .autocomplete input';

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      const appTerminal = await startApplication(
        serverProcessManager,
        'vue-server-valid'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    beforeEach(async () => {
      consoleInterceptor = new BrowserConsoleInterceptor(page, projectName);
      await consoleInterceptor.startSession();

      page.on('request', (request: HTTPRequest) => {
        interceptedRequests.push(request);
      });
    });

    afterEach(async () => {
      page.removeAllListeners('request');
      interceptedRequests = [];
      await consoleInterceptor.endSession();
    });

    afterAll(async () => {
      await undoCommit(serverProcessManager, projectPath, projectName);
      await serverProcessManager.killAllProcesses();
    }, 5 * 60e3);
    // TODO CDX-1017: Remove skip
    it.skip('should not contain console errors nor warnings', async () => {
      await page.goto(searchPageEndpoint(), {
        waitUntil: 'networkidle2',
      });

      expect(consoleInterceptor.interceptedMessages).toEqual([]);
    }, 60e3);

    it('should contain a search page section', async () => {
      await page.goto(searchPageEndpoint(), {
        waitUntil: 'networkidle2',
      });
      await page.waitForSelector(searchboxSelector);

      expect(await page.$('#search-page')).not.toBeNull();
    }, 60e3);

    it('should retrieve the search token on the page load', async () => {
      const tokenResponseListener = page.waitForResponse(tokenServerEndpoint());

      page.goto(searchPageEndpoint());
      await page.waitForSelector(searchboxSelector);

      expect(
        JSON.parse(await (await tokenResponseListener).text())
      ).toMatchObject({
        token: expect.stringMatching(jwtTokenPattern),
      });
    }, 60e3);

    it('should send a search query when the page is loaded', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      await page.waitForSelector(searchboxSelector);

      expect(interceptedRequests.some(isSearchRequestOrResponse)).toBeTruthy();
    }, 60e3);

    it('should send a search query on searchbox submit', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      await page.waitForSelector(searchboxSelector);

      interceptedRequests = [];

      await page.focus(searchboxSelector);
      await page.keyboard.type('my query');
      await page.keyboard.press('Enter');

      await retry(async () => {
        expect(
          interceptedRequests.some(isSearchRequestOrResponse)
        ).toBeTruthy();
      });
    }, 60e3);

    it('should be commited without lint-stage errors', async () => {
      const eslintErrorSpy = jest.fn();

      await commitProject(
        serverProcessManager,
        projectPath,
        projectName,
        eslintErrorSpy
      );

      expect(eslintErrorSpy).not.toBeCalled();
    }, 10e3);
  });

  describe('when starting the server', () => {
    let serverProcessManager: ProcessManager;

    beforeEach(() => {
      serverProcessManager = new ProcessManager();
    });

    afterEach(async () => {
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it(
      'should not have any ESLint warning or error',
      async () => {
        const serverTerminal = await startApplication(
          serverProcessManager,
          'vue-server-eslint'
        );
        const eslintErrorSpy = jest.fn();

        await serverTerminal
          .when(/✖ \d+ problems \(\d+ errors, \d+ warnings\)/)
          .on('stdout')
          .do(eslintErrorSpy)
          .until(waitForAppRunning(serverTerminal));

        expect(eslintErrorSpy).not.toBeCalled();
      },
      5 * 60e3
    );
  });

  describe('when the .env file is missing', () => {
    let serverProcessManager: ProcessManager;

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      deactivateEnvironmentFile(projectPath);
    });

    afterAll(async () => {
      await serverProcessManager.killAllProcesses();
      restoreEnvironmentFile(projectPath);
    }, 30e3);

    it(
      'should not start the application',
      async () => {
        const missingEnvErrorSpy = jest.fn();

        const appTerminal = await startApplication(
          serverProcessManager,
          'vue-server-missing-env'
        );

        await appTerminal
          .when(/\.env file not found in the project root/)
          .on('stderr')
          .do(missingEnvErrorSpy)
          .once();

        expect(missingEnvErrorSpy).toHaveBeenCalled();
      },
      2 * 60e3
    );
  });

  describe('when required environment variables are not defined', () => {
    let serverProcessManager: ProcessManager;
    let envFileContent = '';

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      envFileContent = flushEnvFile(projectPath);
      const appTerminal = await startApplication(
        serverProcessManager,
        'vue-server-invalid'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      overwriteEnvFile(projectPath, envFileContent);
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should redirect the user to an error page', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      expect(page.url()).toEqual(`${searchPageEndpoint()}/error`);
    }, 60e3);
  });

  describe('when the ports are manually specified', () => {
    let serverProcessManager: ProcessManager;
    let hardCodedClientPort: number;
    let hardCodedServerPort: number;

    beforeAll(async () => {
      hardCodedClientPort = await getPort();
      hardCodedServerPort = await getPort();
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      forceApplicationPorts(hardCodedClientPort, hardCodedServerPort);

      const appTerminal = await startApplication(
        serverProcessManager,
        'vue-server-port-test'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should run the application on the specified port', async () => {
      expect(clientPort).toEqual(hardCodedClientPort);
    }, 60e3);

    it('should run the token server on the specified port', async () => {
      expect(serverPort).toEqual(hardCodedServerPort);
    }, 60e3);
  });

  describe('when the ports are busy', () => {
    const dummyServers: DummyServer[] = [];
    let serverProcessManager: ProcessManager;
    let usedClientPort: number;
    let usedServerPort: number;

    beforeAll(async () => {
      usedClientPort = await getPort();
      usedServerPort = await getPort();
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      forceApplicationPorts(usedClientPort, usedServerPort);

      dummyServers.push(
        new DummyServer(usedClientPort),
        new DummyServer(usedServerPort)
      );

      const appTerminal = await startApplication(
        serverProcessManager,
        'vue-server-port-test'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      await Promise.all(dummyServers.map((server) => server.close()));
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should allocate a new port for the application', async () => {
      expect(clientPort).not.toEqual(usedClientPort);
    });

    it('should not use an undefined port for application', async () => {
      expect(clientPort).not.toBeUndefined();
    });

    it('should allocate a new port for the token server', async () => {
      expect(serverPort).not.toEqual(usedServerPort);
    });

    it('should not use an undefined port for token server', async () => {
      expect(serverPort).not.toBeUndefined();
    });

    it('should run the application on a new port', async () => {
      await expect(
        page.goto(searchPageEndpoint(), {waitUntil: 'load'})
      ).resolves.not.toThrow();
    });

    it('should run the server on a new port', async () => {
      const tokenRequest = await axios.get(tokenServerEndpoint());
      expect(tokenRequest.data.token).toMatch(jwtTokenPattern);
    });
  });

  it.todo('should create a Vue.js project with a custom preset');
});
Example #28
Source File: react.specs.ts    From cli with Apache License 2.0 4 votes vote down vote up
describe('ui:create:react', () => {
  let browser: Browser;
  const processManagers: ProcessManager[] = [];
  let page: Page;
  const oldEnv = process.env;
  const parentDir = 'react';
  const projectName = `${process.env.TEST_RUN_ID}-react-project`;
  const projectPath = join(getUIProjectPath(), parentDir, projectName);
  let clientPort: number;
  let serverPort: number;

  const searchPageEndpoint = () => `http://localhost:${clientPort}`;

  const tokenServerEndpoint = () => `http://localhost:${serverPort}/token`;

  const waitForAppRunning = (appTerminal: Terminal) =>
    appTerminal
      .when(/You can now view .*-react-project in the browser/)
      .on('stdout')
      .do()
      .once();

  const forceApplicationPorts = (clientPort: number, serverPort: number) => {
    const envPath = getPathToEnvFile(projectPath);
    const environment = parse(readFileSync(envPath, {encoding: 'utf-8'}));

    const updatedEnvironment = {
      ...environment,
      PORT: clientPort,
      REACT_APP_SERVER_PORT: serverPort,
    };
    truncateSync(envPath);
    for (const [key, value] of Object.entries(updatedEnvironment)) {
      appendFileSync(envPath, `${key}=${value}${EOL}`);
    }
  };

  const getAllocatedPorts = () => {
    const envVariables = parse(
      readFileSync(getPathToEnvFile(projectPath), {encoding: 'utf-8'})
    );

    if (!envVariables) {
      throw new Error('Unable to load project environment variables');
    }

    clientPort = parseInt(envVariables.PORT);
    serverPort = parseInt(envVariables.REACT_APP_SERVER_PORT);

    return [clientPort, serverPort];
  };

  const buildApplication = async (processManager: ProcessManager) => {
    const buildTerminal = await setupUIProject(
      processManager,
      'ui:create:react',
      projectName,
      {projectDir: projectPath}
    );

    await buildTerminal.when('exit').on('process').do().once();
  };

  const startApplication = async (
    processManager: ProcessManager,
    debugName = 'react-server'
  ) => {
    const args = [...npm(), 'run', 'start'];
    const serverTerminal = new Terminal(
      args.shift()!,
      args,
      {
        cwd: projectPath,
      },
      processManager,
      debugName
    );
    return serverTerminal;
  };

  beforeAll(async () => {
    await loginWithApiKey(
      process.env.PLATFORM_API_KEY!,
      process.env.ORG_ID!,
      process.env.PLATFORM_ENV!
    );
    const buildProcessManager = new ProcessManager();
    processManagers.push(buildProcessManager);
    browser = await getNewBrowser();
    await buildApplication(buildProcessManager);
    await buildProcessManager.killAllProcesses();
  }, 15 * 60e3);

  beforeEach(async () => {
    jest.resetModules();
    process.env = {...oldEnv};
    page = await openNewPage(browser, page);
  });

  afterEach(async () => {
    await captureScreenshots(browser);
  });

  afterAll(async () => {
    process.env = oldEnv;
    await browser.close();
    await Promise.all(
      processManagers.map((manager) => manager.killAllProcesses())
    );
  });

  describe('when the project is configured correctly', () => {
    let serverProcessManager: ProcessManager;
    let interceptedRequests: HTTPRequest[] = [];
    let consoleInterceptor: BrowserConsoleInterceptor;
    const searchboxSelector = 'div.App .MuiAutocomplete-root input';

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      const appTerminal = await startApplication(
        serverProcessManager,
        'react-server-valid'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 10 * 60e3);

    beforeEach(async () => {
      consoleInterceptor = new BrowserConsoleInterceptor(page, projectPath);
      await consoleInterceptor.startSession();

      page.on('request', (request: HTTPRequest) => {
        interceptedRequests.push(request);
      });
    });

    afterEach(async () => {
      page.removeAllListeners('request');
      interceptedRequests = [];
      await consoleInterceptor.endSession();
    });

    afterAll(async () => {
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    // TODO CDX-1017: Remove skip
    it.skip(
      'should not contain console errors nor warnings',
      async () => {
        await page.goto(searchPageEndpoint(), {
          waitUntil: 'networkidle2',
        });

        expect(consoleInterceptor.interceptedMessages).toEqual([]);
      },
      5 * 60e3
    );

    it('should contain a search page section', async () => {
      await page.goto(searchPageEndpoint(), {
        waitUntil: 'networkidle2',
      });
      await page.waitForSelector(searchboxSelector);

      expect(await page.$('div.App')).not.toBeNull();
    });

    it('should retrieve the search token on the page load', async () => {
      const tokenResponseListener = page.waitForResponse(tokenServerEndpoint());

      page.goto(searchPageEndpoint());
      await page.waitForSelector(searchboxSelector);

      expect(
        JSON.parse(await (await tokenResponseListener).text())
      ).toMatchObject({
        token: expect.stringMatching(jwtTokenPattern),
      });
    });

    it('should send a search query when the page is loaded', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      await page.waitForSelector(searchboxSelector);

      expect(interceptedRequests.some(isSearchRequestOrResponse)).toBeTruthy();
    });

    it('should send a search query on searchbox submit', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      await page.waitForSelector(searchboxSelector);

      interceptedRequests = [];

      await page.focus(searchboxSelector);
      await page.keyboard.type('my query');
      await page.keyboard.press('Enter');

      await retry(async () => {
        expect(
          interceptedRequests.some(isSearchRequestOrResponse)
        ).toBeTruthy();
      });
    });
    it('should have a clean working directory', async () => {
      const gitDirtyWorkingTreeSpy = jest.fn();

      await isDirectoryClean(
        serverProcessManager,
        projectPath,
        projectName,
        gitDirtyWorkingTreeSpy
      );

      expect(gitDirtyWorkingTreeSpy).not.toBeCalled();
    }, 10e3);
  });

  describe('when the .env file is missing', () => {
    let serverProcessManager: ProcessManager;

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      deactivateEnvironmentFile(projectPath);
    });

    afterAll(async () => {
      restoreEnvironmentFile(projectPath);
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it(
      'should not start the application',
      async () => {
        const missingEnvErrorSpy = jest.fn();

        const appTerminal = await startApplication(
          serverProcessManager,
          'react-server-missing-env'
        );

        await appTerminal
          .when(/\.env file not found in the project root/)
          .on('stderr')
          .do(missingEnvErrorSpy)
          .once();

        expect(missingEnvErrorSpy).toHaveBeenCalled();
      },
      10 * 60e3
    );
  });

  describe('when required environment variables are not defined', () => {
    let serverProcessManager: ProcessManager;
    let envFileContent = '';
    const errorMessageSelector = 'div.container';

    beforeAll(async () => {
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      envFileContent = flushEnvFile(projectPath);
      overwriteEnvFile(projectPath, 'GENERATE_SOURCEMAP=false'); // TODO: CDX-737: fix exponential-backoff compilation warnings
      const appTerminal = await startApplication(
        serverProcessManager,
        'react-server-invalid'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      overwriteEnvFile(projectPath, envFileContent);
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should redirect the user to an error page', async () => {
      await page.goto(searchPageEndpoint(), {waitUntil: 'networkidle2'});
      const pageErrorMessage = await page.$eval(
        errorMessageSelector,
        (el) => el.textContent
      );
      expect(pageErrorMessage).toContain(
        'You should have a valid .env file at the root of this project'
      );
    });
  });

  describe('when the ports are manually specified', () => {
    let serverProcessManager: ProcessManager;
    let hardCodedClientPort: number;
    let hardCodedServerPort: number;

    beforeAll(async () => {
      hardCodedClientPort = await getPort();
      hardCodedServerPort = await getPort();
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      forceApplicationPorts(hardCodedClientPort, hardCodedServerPort);

      const appTerminal = await startApplication(
        serverProcessManager,
        'react-server-port-test'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should run the application on the specified port', async () => {
      expect(clientPort).toEqual(hardCodedClientPort);
    });

    it('should run the token server on the specified port', async () => {
      expect(serverPort).toEqual(hardCodedServerPort);
    });
  });

  describe('when the ports are busy', () => {
    const dummyServers: DummyServer[] = [];
    let serverProcessManager: ProcessManager;
    let usedClientPort: number;
    let usedServerPort: number;

    beforeAll(async () => {
      usedClientPort = await getPort();
      usedServerPort = await getPort();
      serverProcessManager = new ProcessManager();
      processManagers.push(serverProcessManager);
      forceApplicationPorts(usedClientPort, usedServerPort);

      dummyServers.push(
        new DummyServer(usedClientPort),
        new DummyServer(usedServerPort)
      );

      const appTerminal = await startApplication(
        serverProcessManager,
        'react-server-port-test'
      );
      await waitForAppRunning(appTerminal);
      [clientPort, serverPort] = getAllocatedPorts();
    }, 2 * 60e3);

    afterAll(async () => {
      await Promise.all(dummyServers.map((server) => server.close()));
      await serverProcessManager.killAllProcesses();
    }, 30e3);

    it('should allocate a new port for the application', async () => {
      expect(clientPort).not.toEqual(usedClientPort);
    });

    it('should not use an undefined port for application', async () => {
      expect(clientPort).not.toBeUndefined();
    });

    it('should allocate a new port for the token server', async () => {
      expect(serverPort).not.toEqual(usedServerPort);
    });

    it('should not use an undefined port for token server', async () => {
      expect(serverPort).not.toBeUndefined();
    });

    it('should run the application on a new port', async () => {
      await expect(
        page.goto(searchPageEndpoint(), {waitUntil: 'load'})
      ).resolves.not.toThrow();
    });

    it('should run the server on a new port', async () => {
      const tokenRequest = await axios.get(tokenServerEndpoint());
      expect(tokenRequest.data.token).toMatch(jwtTokenPattern);
    });
  });
});
Example #29
Source File: atomic.specs.ts    From cli with Apache License 2.0 4 votes vote down vote up
describe('ui:create:atomic', () => {
  const searchPageEndpoint = 'http://localhost:8888';
  const tokenServerEndpoint = 'http://localhost:8888/.netlify/functions/token';
  const searchInterfaceSelector = 'atomic-search-interface';

  const waitForAppRunning = (appTerminal: Terminal) =>
    appTerminal
      .when(/build finished/)
      .on('stdout')
      .do()
      .once();

  const getProjectName = (id: string) =>
    `${process.env.TEST_RUN_ID}-atomic-project-${id}`;

  const buildApplication = async (
    processManager: ProcessManager,
    options: BuildAppOptions
  ) => {
    let stderr = '';
    const stderrListener = (chunk: string) => {
      stderr += chunk;
    };

    const buildTerminal = await setupUIProject(
      processManager,
      'ui:create:atomic',
      getProjectName(options.id),
      {flags: options.pageId ? ['--pageId', options.pageId] : undefined}
    );

    buildTerminal.orchestrator.process.stderr.on('data', stderrListener);

    if (!options.pageId) {
      await buildTerminal
        .when(/Use an existing hosted search page/)
        .on('stdout')
        .do(answerPrompt(options.promptAnswer ? options.promptAnswer : EOL))
        .once();
    }

    const buildTerminalExitPromise = Promise.race([
      buildTerminal.when('exit').on('process').do().once(),
      buildTerminal
        .when(/Happy hacking!/)
        .on('stdout')
        .do()
        .once(),
    ]);

    await buildTerminal
      .when(/\(y\)/)
      .on('stderr')
      .do(answerPrompt(`y${EOL}`))
      .until(buildTerminalExitPromise);

    return {stderr};
  };

  const startApplication = async (
    processManager: ProcessManager,
    options: BuildAppOptions,
    debugName = 'atomic-server'
  ) => {
    const args = [...npm(), 'run', 'start'];

    const serverTerminal = new Terminal(
      args.shift()!,
      args,
      {
        cwd: getProjectPath(getProjectName(options.id)),
      },
      processManager,
      `${debugName}-${options.id}`
    );
    return serverTerminal;
  };

  describe.each([
    {
      describeName: 'when using the default page config (pageId not specified)',
      buildAppOptions: {id: 'without-page-id'},
    },
    {
      describeName: 'when using an existing pageId (--pageId flag specified)',
      buildAppOptions: {
        id: 'with-page-id',
        pageId: 'fffaafcc-6863-46cb-aca3-97522fcc0f5d',
      },
    },
    {
      describeName:
        'when using an existing pageId (using the list prompt of available pages)',
      buildAppOptions: {
        id: 'with-page-id-prompt',
        promptAnswer: `\x1B[B ${EOL}`,
      },
    },
  ])('$describeName', ({buildAppOptions}) => {
    const oldEnv = process.env;
    const processManagers: ProcessManager[] = [];
    let stderr: string;
    let browser: Browser;
    let page: Page;

    function projectFileExist(path: string) {
      return existsSync(
        join(
          getProjectPath(getProjectName(buildAppOptions.id)),
          ...path.split('/')
        )
      );
    }

    beforeAll(async () => {
      await loginWithApiKey(
        process.env.PLATFORM_API_KEY!,
        process.env.ORG_ID!,
        process.env.PLATFORM_ENV!
      );
      const processManager = new ProcessManager();
      processManagers.push(processManager);
      browser = await getNewBrowser();
      stderr = (await buildApplication(processManager, buildAppOptions)).stderr;
      await processManager.killAllProcesses();
    }, 15 * 60e3);

    beforeEach(async () => {
      jest.resetModules();
      process.env = {...oldEnv};
      page = await openNewPage(browser, page);
    });

    afterEach(async () => {
      await captureScreenshots(browser);
    });

    afterAll(async () => {
      process.env = oldEnv;
      await browser.close();
      await Promise.all(
        processManagers.map((manager) => manager.killAllProcesses())
      );
    }, 5 * 30e3);

    it('should use the right configuration', () => {
      const message =
        buildAppOptions.promptAnswer || buildAppOptions.pageId
          ? 'Hosted search page named'
          : 'Using the default search page template.';
      expect(stderr).toContain(message);
    });

    describe('validating files', () => {
      const createdFilesPaths = [
        'package.json',
        'package-lock.json',
        '.gitignore',
        '.env',
        '.env.example',
        'README.md',
        'start-netlify.mjs',
        'netlify.toml',
        'tsconfig.json',
        'stencil.config.ts',
        'lambda',
        'src/index.ts',
        'src/html.d.ts',
        'src/components.d.ts',
        'src/style/index.css',
        'src/pages/index.html',
        'src/components/results-manager/results-manager.tsx',
        'src/components/results-manager/template-1.html',
        'src/components/sample-component/sample-component.tsx',
        'src/components/sample-component/sample-component.css',
        'src/components/sample-result-component/sample-result-component.tsx',
        'src/components/sample-result-component/sample-result-component.css',
      ];

      test.each(createdFilesPaths)(
        'should create the %s file or directory',
        (path) => expect(projectFileExist(path)).toBe(true)
      );

      const deletedFilesPaths = [
        'scripts/clean-up.js',
        'scripts/setup-lamdba.js',
        'scripts/utils.js',
      ];

      test.each(deletedFilesPaths)(
        'should delete the %s file or directory',
        (path) => expect(projectFileExist(path)).toBe(false)
      );
    });

    describe('when the project is configured correctly', () => {
      let serverProcessManager: ProcessManager;
      let interceptedRequests: HTTPRequest[] = [];
      let consoleInterceptor: BrowserConsoleInterceptor;

      beforeAll(async () => {
        serverProcessManager = new ProcessManager();
        processManagers.push(serverProcessManager);
        const appTerminal = await startApplication(
          serverProcessManager,
          buildAppOptions,
          'atomic-server-valid'
        );
        await waitForAppRunning(appTerminal);
      }, 5 * 60e3);

      beforeEach(async () => {
        consoleInterceptor = new BrowserConsoleInterceptor(
          page,
          getProjectName(buildAppOptions.id)
        );
        await consoleInterceptor.startSession();

        page.on('request', (request: HTTPRequest) => {
          interceptedRequests.push(request);
        });
      });

      afterEach(async () => {
        page.removeAllListeners('request');
        interceptedRequests = [];
        await consoleInterceptor.endSession();
      });

      afterAll(async () => {
        await serverProcessManager.killAllProcesses();
      }, 5 * 30e3);

      it.skip('should not contain console errors nor warnings', async () => {
        await page.goto(searchPageEndpoint, {
          waitUntil: 'networkidle2',
        });

        expect(consoleInterceptor.interceptedMessages).toEqual([]);
      }, 60e3);

      it('should contain a search page section', async () => {
        await page.goto(searchPageEndpoint, {
          waitUntil: 'networkidle2',
        });

        expect(await page.$(searchInterfaceSelector)).not.toBeNull();
      }, 60e3);

      it('should retrieve the search token on the page load', async () => {
        const tokenResponseListener = page.waitForResponse(tokenServerEndpoint);
        page.goto(searchPageEndpoint);
        await page.waitForSelector(searchInterfaceSelector);

        expect(
          JSON.parse(await (await tokenResponseListener).text())
        ).toMatchObject({
          token: expect.stringMatching(jwtTokenPattern),
        });
      }, 60e3);

      it('should send a search query when the page is loaded', async () => {
        await page.goto(searchPageEndpoint, {waitUntil: 'networkidle2'});
        await page.waitForSelector(searchInterfaceSelector);

        expect(
          interceptedRequests.some(isSearchRequestOrResponse)
        ).toBeTruthy();
      }, 60e3);
    });

    describe('when the default Stencil port is busy', () => {
      let dummyServer: DummyServer;
      let serverProcessManager: ProcessManager;

      beforeAll(async () => {
        serverProcessManager = new ProcessManager();
        processManagers.push(serverProcessManager);

        dummyServer = new DummyServer(3333);

        const appTerminal = await startApplication(
          serverProcessManager,
          buildAppOptions,
          'stencil-port-test'
        );
        await waitForAppRunning(appTerminal);
      }, 2 * 60e3);

      afterAll(async () => {
        await dummyServer.close();
        await serverProcessManager.killAllProcesses();
      }, 30e3);

      it('Netlify should still load the Stencil app properly', async () => {
        await page.goto(searchPageEndpoint, {
          waitUntil: 'networkidle2',
        });

        expect(await page.$(searchInterfaceSelector)).not.toBeNull();
      }, 60e3);
    });
  });
});