/**
 * Copyright Microsoft Corporation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { test, expect, countTimes } from './playwright-test-fixtures';

test('test.describe.parallel should throw inside test.describe.serial', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe.serial('serial suite', () => {
        test.describe.parallel('parallel suite', () => {
        });
      });
    `,
  });
  expect(result.exitCode).toBe(1);
  expect(result.output).toContain('Error: describe.parallel cannot be nested inside describe.serial');
  expect(result.output).toContain('a.test.ts:4');
});

test('test.describe.parallel should work', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe.parallel('parallel suite', () => {
        test('test1', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
        test('test2', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
        test.describe('inner suite', () => {
          test('test3', async ({}, testInfo) => {
            console.log('\\n%% worker=' + testInfo.workerIndex);
            await new Promise(f => setTimeout(f, 1000));
          });
        });
      });
    `,
  }, { workers: 3 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(3);
  expect(result.output).toContain('%% worker=0');
  expect(result.output).toContain('%% worker=1');
  expect(result.output).toContain('%% worker=2');
});

test('test.describe.parallel should work in file', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe.configure({ mode: 'parallel' });
      test('test1', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test('test2', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test.describe('inner suite', () => {
        test('test3', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
      });
    `,
  }, { workers: 3 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(3);
  expect(result.output).toContain('%% worker=0');
  expect(result.output).toContain('%% worker=1');
  expect(result.output).toContain('%% worker=2');
});

test('test.describe.parallel should work in describe', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe('parallel suite', () => {
        test.describe.configure({ mode: 'parallel' });
        test('test1', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
        test('test2', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
        test.describe('inner suite', () => {
          test('test3', async ({}, testInfo) => {
            console.log('\\n%% worker=' + testInfo.workerIndex);
            await new Promise(f => setTimeout(f, 1000));
          });
        });
      });
    `,
  }, { workers: 3 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(3);
  expect(result.output).toContain('%% worker=0');
  expect(result.output).toContain('%% worker=1');
  expect(result.output).toContain('%% worker=2');
});

test('config.fullyParallel should work', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'playwright.config.ts': `
      module.exports = { fullyParallel: true };
    `,
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test('test1', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test('test2', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test.describe('inner suite', () => {
        test('test3', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
      });
    `,
  }, { workers: 3 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(3);
  expect(result.output).toContain('%% worker=0');
  expect(result.output).toContain('%% worker=1');
  expect(result.output).toContain('%% worker=2');
});

test('project.fullyParallel should work', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'playwright.config.ts': `
      module.exports = { projects: [ { fullyParallel: true } ] };
    `,
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test('test1', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test('test2', async ({}, testInfo) => {
        console.log('\\n%% worker=' + testInfo.workerIndex);
        await new Promise(f => setTimeout(f, 1000));
      });
      test.describe('inner suite', () => {
        test('test3', async ({}, testInfo) => {
          console.log('\\n%% worker=' + testInfo.workerIndex);
          await new Promise(f => setTimeout(f, 1000));
        });
      });
    `,
  }, { workers: 3 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(3);
  expect(result.output).toContain('%% worker=0');
  expect(result.output).toContain('%% worker=1');
  expect(result.output).toContain('%% worker=2');
});

test('parallel mode should minimize running beforeAll/afterAll hooks', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe.configure({ mode: 'parallel' });
      test.beforeAll(() => {
        console.log('\\n%%beforeAll');
      });
      test.afterAll(() => {
        console.log('\\n%%afterAll');
      });
      test('test1', () => {});
      test('test2', () => {});
      test('test3', () => {});
      test('test4', () => {});
    `,
  }, { workers: 1 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(4);
  expect(countTimes(result.output, '%%beforeAll')).toBe(1);
  expect(countTimes(result.output, '%%afterAll')).toBe(1);
});

test('parallel mode should minimize running beforeAll/afterAll hooks 2', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test.describe.configure({ mode: 'parallel' });
      test.beforeAll(() => {
        console.log('\\n%%beforeAll');
      });
      test.afterAll(() => {
        console.log('\\n%%afterAll');
      });
      test('test1', () => {});
      test('test2', () => {});
      test('test3', () => {});
      test('test4', () => {});
    `,
  }, { workers: 2 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(4);
  expect(countTimes(result.output, '%%beforeAll')).toBe(2);
  expect(countTimes(result.output, '%%afterAll')).toBe(2);
});

test('parallel mode should display worker count taking project workers into account', async ({ runInlineTest }) => {
  const result = await runInlineTest({
    'playwright.config.ts': `
      export default {
        workers: 10,
        fullyParallel: true,
        projects: [
          { name: 'project1', workers: 3 },
        ],
      };
    `,
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test('test1', () => {});
      test('test2', () => {});
      test('test3', () => {});
      test('test4', () => {});
    `,
  }, { workers: 10 });
  expect(result.exitCode).toBe(0);
  expect(result.passed).toBe(4);
  expect(result.output).toContain('Running 4 tests using 3 workers');

  // Multiple parallel projects
  const result2 = await runInlineTest({
    'playwright.config.ts': `
      export default {
        workers: 12,
        fullyParallel: true,
        projects: [
          { name: 'project1', workers: 3 },
          { name: 'project2', workers: 4 },
          { name: 'project3', workers: 4 },
        ],
      };
    `,
    'a.test.ts': `
      import { test, expect } from '@playwright/test';
      test('test1', () => {});
      test('test2', () => {});
      test('test3', () => {});
      test('test4', () => {});
    `,
  }, { workers: 12 });
  expect(result2.exitCode).toBe(0);
  expect(result2.passed).toBe(12);
  expect(result2.output).toContain('Running 12 tests using 11 workers');
});
